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

lightningnetwork / lnd / 14358372723

09 Apr 2025 01:26PM UTC coverage: 56.696% (-12.3%) from 69.037%
14358372723

Pull #9696

github

web-flow
Merge e2837e400 into 867d27d68
Pull Request #9696: Add `development_guidelines.md` for both human and machine

107055 of 188823 relevant lines covered (56.7%)

22721.56 hits per line

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

79.63
/channeldb/reports.go
1
package channeldb
2

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

8
        "github.com/btcsuite/btcd/btcutil"
9
        "github.com/btcsuite/btcd/chaincfg/chainhash"
10
        "github.com/btcsuite/btcd/wire"
11
        graphdb "github.com/lightningnetwork/lnd/graph/db"
12
        "github.com/lightningnetwork/lnd/kvdb"
13
        "github.com/lightningnetwork/lnd/tlv"
14
)
15

16
var (
17
        // closeSummaryBucket is a top level bucket which holds additional
18
        // information about channel closes. It nests channels by chainhash
19
        // and channel point.
20
        // [closeSummaryBucket]
21
        //        [chainHashBucket]
22
        //                [channelBucket]
23
        //                        [resolversBucket]
24
        closeSummaryBucket = []byte("close-summaries")
25

26
        // resolversBucket holds the outcome of a channel's resolvers. It is
27
        // nested under a channel and chainhash bucket in the close summaries
28
        // bucket.
29
        resolversBucket = []byte("resolvers-bucket")
30
)
31

32
var (
33
        // ErrNoChainHashBucket is returned when we have not created a bucket
34
        // for the current chain hash.
35
        ErrNoChainHashBucket = errors.New("no chain hash bucket")
36

37
        // ErrNoChannelSummaries is returned when a channel is not found in the
38
        // chain hash bucket.
39
        ErrNoChannelSummaries = errors.New("channel bucket not found")
40

41
        amountType    tlv.Type = 1
42
        resolverType  tlv.Type = 2
43
        outcomeType   tlv.Type = 3
44
        spendTxIDType tlv.Type = 4
45
)
46

47
// ResolverType indicates the type of resolver that was resolved on chain.
48
type ResolverType uint8
49

50
const (
51
        // ResolverTypeAnchor represents a resolver for an anchor output.
52
        ResolverTypeAnchor ResolverType = 0
53

54
        // ResolverTypeIncomingHtlc represents resolution of an incoming htlc.
55
        ResolverTypeIncomingHtlc ResolverType = 1
56

57
        // ResolverTypeOutgoingHtlc represents resolution of an outgoing htlc.
58
        ResolverTypeOutgoingHtlc ResolverType = 2
59

60
        // ResolverTypeCommit represents resolution of our time locked commit
61
        // when we force close.
62
        ResolverTypeCommit ResolverType = 3
63
)
64

65
// ResolverOutcome indicates the outcome for the resolver that that the contract
66
// court reached. This state is not necessarily final, since htlcs on our own
67
// commitment are resolved across two resolvers.
68
type ResolverOutcome uint8
69

70
const (
71
        // ResolverOutcomeClaimed indicates that funds were claimed on chain.
72
        ResolverOutcomeClaimed ResolverOutcome = 0
73

74
        // ResolverOutcomeUnclaimed indicates that we did not claim our funds on
75
        // chain. This may be the case for anchors that we did not sweep, or
76
        // outputs that were not economical to sweep.
77
        ResolverOutcomeUnclaimed ResolverOutcome = 1
78

79
        // ResolverOutcomeAbandoned indicates that we did not attempt to claim
80
        // an output on chain. This is the case for htlcs that we could not
81
        // decode to claim, or invoice which we fail when an attempt is made
82
        // to settle them on chain.
83
        ResolverOutcomeAbandoned ResolverOutcome = 2
84

85
        // ResolverOutcomeTimeout indicates that a contract was timed out on
86
        // chain.
87
        ResolverOutcomeTimeout ResolverOutcome = 3
88

89
        // ResolverOutcomeFirstStage indicates that a htlc had to be claimed
90
        // over two stages, with this outcome representing the confirmation
91
        // of our success/timeout tx.
92
        ResolverOutcomeFirstStage ResolverOutcome = 4
93
)
94

95
// ResolverReport provides an account of the outcome of a resolver. This differs
96
// from a ContractReport because it does not necessarily fully resolve the
97
// contract; each step of two stage htlc resolution is included.
98
type ResolverReport struct {
99
        // OutPoint is the on chain outpoint that was spent as a result of this
100
        // resolution. When an output is directly resolved (eg, commitment
101
        // sweeps and single stage htlcs on the remote party's output) this
102
        // is an output on the commitment tx that was broadcast. When we resolve
103
        // across two stages (eg, htlcs on our own force close commit), the
104
        // first stage outpoint is the output on our commitment and the second
105
        // stage output is the spend from our htlc success/timeout tx.
106
        OutPoint wire.OutPoint
107

108
        // Amount is the value of the output referenced above.
109
        Amount btcutil.Amount
110

111
        // ResolverType indicates the type of resolution that occurred.
112
        ResolverType
113

114
        // ResolverOutcome indicates the outcome of the resolver.
115
        ResolverOutcome
116

117
        // SpendTxID is the transaction ID of the spending transaction that
118
        // claimed the outpoint. This may be a sweep transaction, or a first
119
        // stage success/timeout transaction.
120
        SpendTxID *chainhash.Hash
121
}
122

123
// PutResolverReport creates and commits a transaction that is used to write a
124
// resolver report to disk.
125
func (d *DB) PutResolverReport(tx kvdb.RwTx, chainHash chainhash.Hash,
126
        channelOutpoint *wire.OutPoint, report *ResolverReport) error {
3✔
127

3✔
128
        putReportFunc := func(tx kvdb.RwTx) error {
6✔
129
                return putReport(tx, chainHash, channelOutpoint, report)
3✔
130
        }
3✔
131

132
        // If the transaction is nil, we'll create a new one.
133
        if tx == nil {
6✔
134
                return kvdb.Update(d, putReportFunc, func() {})
6✔
135
        }
136

137
        // Otherwise, we can write the report to disk using the existing
138
        // transaction.
139
        return putReportFunc(tx)
×
140
}
141

142
// putReport puts a report in the bucket provided, with its outpoint as its key.
143
func putReport(tx kvdb.RwTx, chainHash chainhash.Hash,
144
        channelOutpoint *wire.OutPoint, report *ResolverReport) error {
3✔
145

3✔
146
        channelBucket, err := fetchReportWriteBucket(
3✔
147
                tx, chainHash, channelOutpoint,
3✔
148
        )
3✔
149
        if err != nil {
3✔
150
                return err
×
151
        }
×
152

153
        // If the resolvers bucket does not exist yet, create it.
154
        resolvers, err := channelBucket.CreateBucketIfNotExists(
3✔
155
                resolversBucket,
3✔
156
        )
3✔
157
        if err != nil {
3✔
158
                return err
×
159
        }
×
160

161
        var valueBuf bytes.Buffer
3✔
162
        if err := serializeReport(&valueBuf, report); err != nil {
3✔
163
                return err
×
164
        }
×
165

166
        // Finally write our outpoint to be used as the key for this record.
167
        var keyBuf bytes.Buffer
3✔
168
        if err := graphdb.WriteOutpoint(&keyBuf, &report.OutPoint); err != nil {
3✔
169
                return err
×
170
        }
×
171

172
        return resolvers.Put(keyBuf.Bytes(), valueBuf.Bytes())
3✔
173
}
174

175
// serializeReport serialized a report using a TLV stream to allow for optional
176
// fields.
177
func serializeReport(w io.Writer, report *ResolverReport) error {
3✔
178
        amt := uint64(report.Amount)
3✔
179
        resolver := uint8(report.ResolverType)
3✔
180
        outcome := uint8(report.ResolverOutcome)
3✔
181

3✔
182
        // Create a set of TLV records for the values we know to be present.
3✔
183
        records := []tlv.Record{
3✔
184
                tlv.MakePrimitiveRecord(amountType, &amt),
3✔
185
                tlv.MakePrimitiveRecord(resolverType, &resolver),
3✔
186
                tlv.MakePrimitiveRecord(outcomeType, &outcome),
3✔
187
        }
3✔
188

3✔
189
        // If our spend txid is non-nil, we add a tlv entry for it.
3✔
190
        if report.SpendTxID != nil {
4✔
191
                var spendBuf bytes.Buffer
1✔
192
                err := WriteElement(&spendBuf, *report.SpendTxID)
1✔
193
                if err != nil {
1✔
194
                        return err
×
195
                }
×
196
                spendBytes := spendBuf.Bytes()
1✔
197

1✔
198
                records = append(records, tlv.MakePrimitiveRecord(
1✔
199
                        spendTxIDType, &spendBytes,
1✔
200
                ))
1✔
201
        }
202

203
        // Create our stream and encode it.
204
        tlvStream, err := tlv.NewStream(records...)
3✔
205
        if err != nil {
3✔
206
                return err
×
207
        }
×
208

209
        return tlvStream.Encode(w)
3✔
210
}
211

212
// FetchChannelReports fetches the set of reports for a channel.
213
func (d DB) FetchChannelReports(chainHash chainhash.Hash,
214
        outPoint *wire.OutPoint) ([]*ResolverReport, error) {
4✔
215

4✔
216
        var reports []*ResolverReport
4✔
217

4✔
218
        if err := kvdb.View(d.Backend, func(tx kvdb.RTx) error {
8✔
219
                chanBucket, err := fetchReportReadBucket(
4✔
220
                        tx, chainHash, outPoint,
4✔
221
                )
4✔
222
                if err != nil {
5✔
223
                        return err
1✔
224
                }
1✔
225

226
                // If there are no resolvers for this channel, we simply
227
                // return nil, because nothing has been persisted yet.
228
                resolvers := chanBucket.NestedReadBucket(resolversBucket)
3✔
229
                if resolvers == nil {
3✔
230
                        return nil
×
231
                }
×
232

233
                // Run through each resolution and add it to our set of
234
                // resolutions.
235
                return resolvers.ForEach(func(k, v []byte) error {
6✔
236
                        // Deserialize the contents of our field.
3✔
237
                        r := bytes.NewReader(v)
3✔
238
                        report, err := deserializeReport(r)
3✔
239
                        if err != nil {
3✔
240
                                return err
×
241
                        }
×
242

243
                        // Once we have read our values out, set the outpoint
244
                        // on the report using the key.
245
                        r = bytes.NewReader(k)
3✔
246
                        if err := ReadElement(r, &report.OutPoint); err != nil {
3✔
247
                                return err
×
248
                        }
×
249

250
                        reports = append(reports, report)
3✔
251

3✔
252
                        return nil
3✔
253
                })
254
        }, func() {
4✔
255
                reports = nil
4✔
256
        }); err != nil {
5✔
257
                return nil, err
1✔
258
        }
1✔
259

260
        return reports, nil
3✔
261
}
262

263
// deserializeReport gets a resolver report from a tlv stream. The outpoint on
264
// the resolver will not be set because we key reports by their outpoint, and
265
// this function reads only the values saved in the stream.
266
func deserializeReport(r io.Reader) (*ResolverReport, error) {
3✔
267
        var (
3✔
268
                resolver, outcome uint8
3✔
269
                amt               uint64
3✔
270
                spentTx           []byte
3✔
271
        )
3✔
272

3✔
273
        tlvStream, err := tlv.NewStream(
3✔
274
                tlv.MakePrimitiveRecord(amountType, &amt),
3✔
275
                tlv.MakePrimitiveRecord(resolverType, &resolver),
3✔
276
                tlv.MakePrimitiveRecord(outcomeType, &outcome),
3✔
277
                tlv.MakePrimitiveRecord(spendTxIDType, &spentTx),
3✔
278
        )
3✔
279
        if err != nil {
3✔
280
                return nil, err
×
281
        }
×
282

283
        if err := tlvStream.Decode(r); err != nil {
3✔
284
                return nil, err
×
285
        }
×
286

287
        report := &ResolverReport{
3✔
288
                Amount:          btcutil.Amount(amt),
3✔
289
                ResolverOutcome: ResolverOutcome(outcome),
3✔
290
                ResolverType:    ResolverType(resolver),
3✔
291
        }
3✔
292

3✔
293
        // If our spend tx is set, we set it on our report.
3✔
294
        if len(spentTx) != 0 {
4✔
295
                spendTx, err := chainhash.NewHash(spentTx)
1✔
296
                if err != nil {
1✔
297
                        return nil, err
×
298
                }
×
299
                report.SpendTxID = spendTx
1✔
300
        }
301

302
        return report, nil
3✔
303
}
304

305
// fetchReportWriteBucket returns a write channel bucket within the reports
306
// top level bucket. If the channel's bucket does not yet exist, it will be
307
// created.
308
func fetchReportWriteBucket(tx kvdb.RwTx, chainHash chainhash.Hash,
309
        outPoint *wire.OutPoint) (kvdb.RwBucket, error) {
7✔
310

7✔
311
        // Get the channel close summary bucket.
7✔
312
        closedBucket := tx.ReadWriteBucket(closeSummaryBucket)
7✔
313

7✔
314
        // Create the chain hash bucket if it does not exist.
7✔
315
        chainHashBkt, err := closedBucket.CreateBucketIfNotExists(chainHash[:])
7✔
316
        if err != nil {
7✔
317
                return nil, err
×
318
        }
×
319

320
        var chanPointBuf bytes.Buffer
7✔
321
        if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
7✔
322
                return nil, err
×
323
        }
×
324

325
        return chainHashBkt.CreateBucketIfNotExists(chanPointBuf.Bytes())
7✔
326
}
327

328
// fetchReportReadBucket returns a read channel bucket within the reports
329
// top level bucket. If any bucket along the way does not exist, it will error.
330
func fetchReportReadBucket(tx kvdb.RTx, chainHash chainhash.Hash,
331
        outPoint *wire.OutPoint) (kvdb.RBucket, error) {
4✔
332

4✔
333
        // First fetch the top level channel close summary bucket.
4✔
334
        closeBucket := tx.ReadBucket(closeSummaryBucket)
4✔
335

4✔
336
        // Next we get the chain hash bucket for our current chain.
4✔
337
        chainHashBucket := closeBucket.NestedReadBucket(chainHash[:])
4✔
338
        if chainHashBucket == nil {
5✔
339
                return nil, ErrNoChainHashBucket
1✔
340
        }
1✔
341

342
        // With the bucket for the node and chain fetched, we can now go down
343
        // another level, for the channel itself.
344
        var chanPointBuf bytes.Buffer
3✔
345
        if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
3✔
346
                return nil, err
×
347
        }
×
348

349
        chanBucket := chainHashBucket.NestedReadBucket(chanPointBuf.Bytes())
3✔
350
        if chanBucket == nil {
3✔
351
                return nil, ErrNoChannelSummaries
×
352
        }
×
353

354
        return chanBucket, nil
3✔
355
}
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