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

lightningnetwork / lnd / 19155841408

07 Nov 2025 02:03AM UTC coverage: 66.675% (-0.04%) from 66.712%
19155841408

Pull #10352

github

web-flow
Merge e4313eba8 into 096ab65b1
Pull Request #10352: [WIP] chainrpc: return Unavailable while notifier starts

137328 of 205965 relevant lines covered (66.68%)

21333.36 hits per line

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

75.0
/channeldb/height_hint.go
1
package channeldb
2

3
import (
4
        "bytes"
5

6
        "github.com/lightningnetwork/lnd/chainntnfs"
7
        "github.com/lightningnetwork/lnd/kvdb"
8
)
9

10
var (
11
        // spendHintBucket is the name of the bucket which houses the height
12
        // hint for outpoints. Each height hint represents the earliest height
13
        // at which its corresponding outpoint could have been spent within.
14
        spendHintBucket = []byte("spend-hints")
15

16
        // confirmHintBucket is the name of the bucket which houses the height
17
        // hints for transactions. Each height hint represents the earliest
18
        // height at which its corresponding transaction could have been
19
        // confirmed within.
20
        confirmHintBucket = []byte("confirm-hints")
21
)
22

23
// CacheConfig contains the HeightHintCache configuration.
24
type CacheConfig struct {
25
        // QueryDisable prevents reliance on the Height Hint Cache.  This is
26
        // necessary to recover from an edge case when the height recorded in
27
        // the cache is higher than the actual height of a spend, causing a
28
        // channel to become "stuck" in a pending close state.
29
        QueryDisable bool
30
}
31

32
// HeightHintCache is an implementation of the SpendHintCache and
33
// ConfirmHintCache interfaces backed by a channeldb DB instance where the hints
34
// will be stored.
35
type HeightHintCache struct {
36
        cfg CacheConfig
37
        db  kvdb.Backend
38
}
39

40
// Compile-time checks to ensure HeightHintCache satisfies the SpendHintCache
41
// and ConfirmHintCache interfaces.
42
var _ chainntnfs.SpendHintCache = (*HeightHintCache)(nil)
43
var _ chainntnfs.ConfirmHintCache = (*HeightHintCache)(nil)
44

45
// NewHeightHintCache returns a new height hint cache backed by a database.
46
func NewHeightHintCache(cfg CacheConfig, db kvdb.Backend) (*HeightHintCache,
47
        error) {
20✔
48

20✔
49
        cache := &HeightHintCache{cfg, db}
20✔
50
        if err := cache.initBuckets(); err != nil {
20✔
51
                return nil, err
×
52
        }
×
53

54
        return cache, nil
20✔
55
}
56

57
// initBuckets ensures that the primary buckets used by the circuit are
58
// initialized so that we can assume their existence after startup.
59
func (c *HeightHintCache) initBuckets() error {
20✔
60
        return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
40✔
61
                _, err := tx.CreateTopLevelBucket(spendHintBucket)
20✔
62
                if err != nil {
20✔
63
                        return err
×
64
                }
×
65

66
                _, err = tx.CreateTopLevelBucket(confirmHintBucket)
20✔
67
                return err
20✔
68
        })
69
}
70

71
// CommitSpendHint commits a spend hint for the outpoints to the cache.
72
func (c *HeightHintCache) CommitSpendHint(height uint32,
73
        spendRequests ...chainntnfs.SpendRequest) error {
1,515✔
74

1,515✔
75
        if len(spendRequests) == 0 {
2,936✔
76
                return nil
1,421✔
77
        }
1,421✔
78

79
        log.Tracef("Updating spend hint to height %d for %v", height,
97✔
80
                spendRequests)
97✔
81

97✔
82
        return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
194✔
83
                spendHints := tx.ReadWriteBucket(spendHintBucket)
97✔
84
                if spendHints == nil {
97✔
85
                        return chainntnfs.ErrCorruptedHeightHintCache
×
86
                }
×
87

88
                var hint bytes.Buffer
97✔
89
                if err := WriteElement(&hint, height); err != nil {
97✔
90
                        return err
×
91
                }
×
92

93
                for _, spendRequest := range spendRequests {
198✔
94
                        spendRequest := spendRequest
101✔
95
                        spendHintKey, err := spendHintKey(&spendRequest)
101✔
96
                        if err != nil {
101✔
97
                                return err
×
98
                        }
×
99
                        err = spendHints.Put(spendHintKey, hint.Bytes())
101✔
100
                        if err != nil {
101✔
101
                                return err
×
102
                        }
×
103
                }
104

105
                return nil
97✔
106
        })
107
}
108

109
// QuerySpendHint returns the latest spend hint for an outpoint.
110
// ErrSpendHintNotFound is returned if a spend hint does not exist within the
111
// cache for the outpoint.
112
func (c *HeightHintCache) QuerySpendHint(
113
        spendRequest chainntnfs.SpendRequest) (uint32, error) {
119✔
114

119✔
115
        var hint uint32
119✔
116
        if c.cfg.QueryDisable {
120✔
117
                log.Debugf("Ignoring spend height hint for %v (height hint "+
1✔
118
                        "cache query disabled)", spendRequest)
1✔
119
                return 0, nil
1✔
120
        }
1✔
121
        err := kvdb.View(c.db, func(tx kvdb.RTx) error {
236✔
122
                spendHints := tx.ReadBucket(spendHintBucket)
118✔
123
                if spendHints == nil {
118✔
124
                        return chainntnfs.ErrCorruptedHeightHintCache
×
125
                }
×
126

127
                spendHintKey, err := spendHintKey(&spendRequest)
118✔
128
                if err != nil {
118✔
129
                        return err
×
130
                }
×
131
                spendHint := spendHints.Get(spendHintKey)
118✔
132
                if spendHint == nil {
199✔
133
                        return chainntnfs.ErrSpendHintNotFound
81✔
134
                }
81✔
135

136
                return ReadElement(bytes.NewReader(spendHint), &hint)
40✔
137
        }, func() {
118✔
138
                hint = 0
118✔
139
        })
118✔
140
        if err != nil {
199✔
141
                return 0, err
81✔
142
        }
81✔
143

144
        return hint, nil
40✔
145
}
146

147
// PurgeSpendHint removes the spend hint for the outpoints from the cache.
148
func (c *HeightHintCache) PurgeSpendHint(
149
        spendRequests ...chainntnfs.SpendRequest) error {
1✔
150

1✔
151
        if len(spendRequests) == 0 {
1✔
152
                return nil
×
153
        }
×
154

155
        log.Tracef("Removing spend hints for %v", spendRequests)
1✔
156

1✔
157
        return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
2✔
158
                spendHints := tx.ReadWriteBucket(spendHintBucket)
1✔
159
                if spendHints == nil {
1✔
160
                        return chainntnfs.ErrCorruptedHeightHintCache
×
161
                }
×
162

163
                for _, spendRequest := range spendRequests {
6✔
164
                        spendRequest := spendRequest
5✔
165
                        spendHintKey, err := spendHintKey(&spendRequest)
5✔
166
                        if err != nil {
5✔
167
                                return err
×
168
                        }
×
169
                        if err := spendHints.Delete(spendHintKey); err != nil {
5✔
170
                                return err
×
171
                        }
×
172
                }
173

174
                return nil
1✔
175
        })
176
}
177

178
// CommitConfirmHint commits a confirm hint for the transactions to the cache.
179
func (c *HeightHintCache) CommitConfirmHint(height uint32,
180
        confRequests ...chainntnfs.ConfRequest) error {
1,634✔
181

1,634✔
182
        if len(confRequests) == 0 {
2,743✔
183
                return nil
1,109✔
184
        }
1,109✔
185

186
        log.Tracef("Updating confirm hints to height %d for %v", height,
528✔
187
                confRequests)
528✔
188

528✔
189
        return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
1,056✔
190
                confirmHints := tx.ReadWriteBucket(confirmHintBucket)
528✔
191
                if confirmHints == nil {
528✔
192
                        return chainntnfs.ErrCorruptedHeightHintCache
×
193
                }
×
194

195
                var hint bytes.Buffer
528✔
196
                if err := WriteElement(&hint, height); err != nil {
528✔
197
                        return err
×
198
                }
×
199

200
                for _, confRequest := range confRequests {
1,473✔
201
                        confRequest := confRequest
945✔
202
                        confHintKey, err := confHintKey(&confRequest)
945✔
203
                        if err != nil {
945✔
204
                                return err
×
205
                        }
×
206
                        err = confirmHints.Put(confHintKey, hint.Bytes())
945✔
207
                        if err != nil {
945✔
208
                                return err
×
209
                        }
×
210
                }
211

212
                return nil
528✔
213
        })
214
}
215

216
// QueryConfirmHint returns the latest confirm hint for a transaction hash.
217
// ErrConfirmHintNotFound is returned if a confirm hint does not exist within
218
// the cache for the transaction hash.
219
func (c *HeightHintCache) QueryConfirmHint(
220
        confRequest chainntnfs.ConfRequest) (uint32, error) {
207✔
221

207✔
222
        var hint uint32
207✔
223
        if c.cfg.QueryDisable {
208✔
224
                log.Debugf("Ignoring confirmation height hint for %v (height "+
1✔
225
                        "hint cache query disabled)", confRequest)
1✔
226
                return 0, nil
1✔
227
        }
1✔
228
        err := kvdb.View(c.db, func(tx kvdb.RTx) error {
412✔
229
                confirmHints := tx.ReadBucket(confirmHintBucket)
206✔
230
                if confirmHints == nil {
206✔
231
                        return chainntnfs.ErrCorruptedHeightHintCache
×
232
                }
×
233

234
                confHintKey, err := confHintKey(&confRequest)
206✔
235
                if err != nil {
206✔
236
                        return err
×
237
                }
×
238
                confirmHint := confirmHints.Get(confHintKey)
206✔
239
                if confirmHint == nil {
387✔
240
                        return chainntnfs.ErrConfirmHintNotFound
181✔
241
                }
181✔
242

243
                return ReadElement(bytes.NewReader(confirmHint), &hint)
28✔
244
        }, func() {
206✔
245
                hint = 0
206✔
246
        })
206✔
247
        if err != nil {
387✔
248
                return 0, err
181✔
249
        }
181✔
250

251
        return hint, nil
28✔
252
}
253

254
// PurgeConfirmHint removes the confirm hint for the transactions from the
255
// cache.
256
func (c *HeightHintCache) PurgeConfirmHint(
257
        confRequests ...chainntnfs.ConfRequest) error {
1✔
258

1✔
259
        if len(confRequests) == 0 {
1✔
260
                return nil
×
261
        }
×
262

263
        log.Tracef("Removing confirm hints for %v", confRequests)
1✔
264

1✔
265
        return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
2✔
266
                confirmHints := tx.ReadWriteBucket(confirmHintBucket)
1✔
267
                if confirmHints == nil {
1✔
268
                        return chainntnfs.ErrCorruptedHeightHintCache
×
269
                }
×
270

271
                for _, confRequest := range confRequests {
6✔
272
                        confRequest := confRequest
5✔
273
                        confHintKey, err := confHintKey(&confRequest)
5✔
274
                        if err != nil {
5✔
275
                                return err
×
276
                        }
×
277
                        if err := confirmHints.Delete(confHintKey); err != nil {
5✔
278
                                return err
×
279
                        }
×
280
                }
281

282
                return nil
1✔
283
        })
284
}
285

286
// confHintKey returns the key that will be used to index the confirmation
287
// request's hint within the height hint cache.
288
func confHintKey(r *chainntnfs.ConfRequest) ([]byte, error) {
1,153✔
289
        if r.TxID == chainntnfs.ZeroHash {
1,384✔
290
                return r.PkScript.Script(), nil
231✔
291
        }
231✔
292

293
        var txid bytes.Buffer
922✔
294
        if err := WriteElement(&txid, r.TxID); err != nil {
922✔
295
                return nil, err
×
296
        }
×
297

298
        return txid.Bytes(), nil
922✔
299
}
300

301
// spendHintKey returns the key that will be used to index the spend request's
302
// hint within the height hint cache.
303
func spendHintKey(r *chainntnfs.SpendRequest) ([]byte, error) {
221✔
304
        if r.OutPoint == chainntnfs.ZeroOutPoint {
319✔
305
                return r.PkScript.Script(), nil
98✔
306
        }
98✔
307

308
        var outpoint bytes.Buffer
123✔
309
        err := WriteElement(&outpoint, r.OutPoint)
123✔
310
        if err != nil {
123✔
311
                return nil, err
×
312
        }
×
313

314
        return outpoint.Bytes(), nil
123✔
315
}
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