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

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 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 {
3✔
64
        return SparsePayHash(rHash)
3✔
65
}
3✔
66

67
// Record returns a tlv record for the SparsePayHash.
68
func (s *SparsePayHash) Record() tlv.Record {
3✔
69
        // We use a zero for the type here, as this'll be used along with the
3✔
70
        // RecordT type.
3✔
71
        return tlv.MakeDynamicRecord(
3✔
72
                0, s, s.hashLen,
3✔
73
                sparseHashEncoder, sparseHashDecoder,
3✔
74
        )
3✔
75
}
3✔
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 {
3✔
81
        if bytes.Equal(s[:], lntypes.ZeroHash[:]) {
3✔
UNCOV
82
                return 0
×
UNCOV
83
        }
×
84

85
        return 32
3✔
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 {
3✔
91
        v, ok := val.(*SparsePayHash)
3✔
92
        if !ok {
3✔
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[:]) {
3✔
UNCOV
98
                return nil
×
UNCOV
99
        }
×
100

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

3✔
103
        return tlv.EBytes32(w, vArray, buf)
3✔
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 {
3✔
110

3✔
111
        v, ok := val.(*SparsePayHash)
3✔
112
        if !ok {
3✔
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 {
3✔
UNCOV
118
                return nil
×
UNCOV
119
        }
×
120

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

3✔
123
        return tlv.DBytes32(r, vArray, buf, 32)
3✔
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) {
3✔
174
        records := []tlv.Record{
3✔
175
                h.RHash.Record(),
3✔
176
                h.RefundTimeout.Record(),
3✔
177
                h.OutputIndex.Record(),
3✔
178
                h.Incoming.Record(),
3✔
179
                h.Amt.Record(),
3✔
180
        }
3✔
181

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

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

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

192
        tlv.SortRecords(records)
3✔
193

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

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

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

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

229
        return h, nil
3✔
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 {
3✔
324

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

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

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

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

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

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

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

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

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

387
        logEntrykey := makeLogKey(commit.CommitHeight)
3✔
388
        return bucket.Put(logEntrykey[:], b.Bytes())
3✔
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) {
3✔
395

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

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

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

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

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

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

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

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

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

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

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

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

465
        return nil
3✔
466
}
467

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

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

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

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

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

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

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

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

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

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

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

3✔
527
        for {
6✔
528
                var htlc HTLCEntry
3✔
529

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

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

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

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

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

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

571
                        htlc.HtlcIndex = record
3✔
572
                }
573

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

578
        return htlcs, nil
3✔
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) {
3✔
591

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

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

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

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

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

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

3✔
627
        return record, nil
3✔
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 {
3✔
633
        var b bytes.Buffer
3✔
634
        if err := s.Encode(&b); err != nil {
3✔
635
                return err
×
636
        }
×
637

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

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

648
        return nil
3✔
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) {
3✔
654
        var bodyLen uint64
3✔
655

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

3✔
672
        return s.DecodeWithParsedTypes(lr)
3✔
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) {
3✔
700

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

710
                // Return the error if it doesn't say the log cannot be found.
711
                if err != ErrLogEntryNotFound {
3✔
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)
3✔
718
        if oldBucket != nil {
3✔
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 {
6✔
731
                return nil, nil, ErrNoPastDeltas
3✔
732
        }
3✔
733

734
        // Otherwise, we've tried to query the new bucket but the log cannot be
735
        // found.
736
        return nil, nil, ErrLogEntryNotFound
3✔
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 {
3✔
757
        // Check if the bucket exists and delete it.
3✔
758
        logBucket := chanBucket.NestedReadWriteBucket(
3✔
759
                revocationLogBucket,
3✔
760
        )
3✔
761
        if logBucket != nil {
6✔
762
                err := chanBucket.DeleteNestedBucket(revocationLogBucket)
3✔
763
                if err != nil {
3✔
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(
3✔
771
                revocationLogBucketDeprecated,
3✔
772
        )
3✔
773
        if oldLogBucket != nil {
3✔
UNCOV
774
                err := chanBucket.DeleteNestedBucket(
×
UNCOV
775
                        revocationLogBucketDeprecated,
×
UNCOV
776
                )
×
UNCOV
777
                if err != nil {
×
778
                        return err
×
779
                }
×
780
        }
781

782
        return nil
3✔
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