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

lightningnetwork / lnd / 12986279612

27 Jan 2025 09:51AM UTC coverage: 57.652% (-1.1%) from 58.788%
12986279612

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19578 existing lines in 256 files now uncovered.

103448 of 179434 relevant lines covered (57.65%)

24884.58 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.
UNCOV
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✔
UNCOV
351
                return nil, ErrNoChannelSummaries
×
UNCOV
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