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

lightningnetwork / lnd / 15574102646

11 Jun 2025 01:44AM UTC coverage: 68.554% (+9.9%) from 58.637%
15574102646

Pull #9652

github

web-flow
Merge eb863e46a into 92a5d35cf
Pull Request #9652: lnwallet/chancloser: fix flake in TestRbfCloseClosingNegotiationLocal

11 of 12 new or added lines in 1 file covered. (91.67%)

7276 existing lines in 84 files now uncovered.

134508 of 196208 relevant lines covered (68.55%)

44569.29 hits per line

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

90.09
/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 {
6,968✔
64
        return SparsePayHash(rHash)
6,968✔
65
}
6,968✔
66

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

85
        return 32
6,956✔
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 {
6,976✔
91
        v, ok := val.(*SparsePayHash)
6,976✔
92
        if !ok {
6,976✔
93
                return tlv.NewTypeForEncodingErr(val, "SparsePayHash")
×
UNCOV
94
        }
×
95

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

101
        vArray := (*[32]byte)(v)
6,956✔
102

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

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

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

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

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

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

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

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

192
        tlv.SortRecords(records)
6,976✔
193

6,976✔
194
        return tlv.NewStream(records...)
6,976✔
195
}
196

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

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

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

229
        return h, nil
6,966✔
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,
284
        customBlob fn.Option[tlv.Blob]) RevocationLog {
6✔
285

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

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

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

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

315
        return rl
6✔
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,934✔
324

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

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

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

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

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

362
        for _, htlc := range commit.Htlcs {
20,976✔
363
                // Skip dust HTLCs.
17,046✔
364
                if htlc.OutputIndex < 0 {
27,130✔
365
                        continue
10,084✔
366
                }
367

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

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

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

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

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

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

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

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

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

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

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

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

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

445
        // Write the HTLCs.
446
        return serializeHTLCEntries(w, rl.HTLCEntries)
3,932✔
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,938✔
452
        for _, htlc := range htlcs {
10,914✔
453
                // Create the tlv stream.
6,976✔
454
                tlvStream, err := htlc.toTlvStream()
6,976✔
455
                if err != nil {
6,976✔
UNCOV
456
                        return err
×
UNCOV
457
                }
×
458

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

465
        return nil
3,938✔
466
}
467

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

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

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

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

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

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

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

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

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

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

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

131,120✔
527
        for {
393,360✔
528
                var htlc HTLCEntry
262,240✔
529

262,240✔
530
                customBlob := htlc.CustomBlob.Zero()
262,240✔
531
                htlcIndex := htlcIndexBlob.Zero()
262,240✔
532

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

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

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

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

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

571
                        htlc.HtlcIndex = record
131,126✔
572
                }
573

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

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

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

131,126✔
599
                // htlcIndexVal is the decoded uint64 value.
131,126✔
600
                htlcIndexVal uint64
131,126✔
601
        )
131,126✔
602

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

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

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

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

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

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

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

648
        return nil
10,904✔
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) {
262,282✔
654
        var bodyLen uint64
262,282✔
655

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

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

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

131,166✔
672
        return s.DecodeWithParsedTypes(lr)
131,166✔
673
}
674

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

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

686
        commitReader := bytes.NewReader(commitBytes)
2✔
687
        return deserializeChanCommit(commitReader)
2✔
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) {
76✔
700

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

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

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

724
                // Found an old record and return it.
725
                return nil, &c, nil
2✔
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 {
68✔
731
                return nil, nil, ErrNoPastDeltas
22✔
732
        }
22✔
733

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

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

752
        return logBucket, nil
6✔
753
}
754

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

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

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