github.com/decred/dcrlnd@v0.7.6/chainntnfs/height_hint_cache.go (about) 1 package chainntnfs 2 3 import ( 4 "bytes" 5 "errors" 6 7 "github.com/decred/dcrlnd/channeldb" 8 "github.com/decred/dcrlnd/kvdb" 9 ) 10 11 var ( 12 // spendHintBucket is the name of the bucket which houses the height 13 // hint for outpoints. Each height hint represents the earliest height 14 // at which its corresponding outpoint could have been spent within. 15 spendHintBucket = []byte("spend-hints") 16 17 // confirmHintBucket is the name of the bucket which houses the height 18 // hints for transactions. Each height hint represents the earliest 19 // height at which its corresponding transaction could have been 20 // confirmed within. 21 confirmHintBucket = []byte("confirm-hints") 22 23 // ErrCorruptedHeightHintCache indicates that the on-disk bucketing 24 // structure has altered since the height hint cache instance was 25 // initialized. 26 ErrCorruptedHeightHintCache = errors.New("height hint cache has been " + 27 "corrupted") 28 29 // ErrSpendHintNotFound is an error returned when a spend hint for an 30 // outpoint was not found. 31 ErrSpendHintNotFound = errors.New("spend hint not found") 32 33 // ErrConfirmHintNotFound is an error returned when a confirm hint for a 34 // transaction was not found. 35 ErrConfirmHintNotFound = errors.New("confirm hint not found") 36 ) 37 38 // CacheConfig contains the HeightHintCache configuration 39 type CacheConfig struct { 40 // QueryDisable prevents reliance on the Height Hint Cache. This is 41 // necessary to recover from an edge case when the height recorded in 42 // the cache is higher than the actual height of a spend, causing a 43 // channel to become "stuck" in a pending close state. 44 QueryDisable bool 45 } 46 47 // SpendHintCache is an interface whose duty is to cache spend hints for 48 // outpoints. A spend hint is defined as the earliest height in the chain at 49 // which an outpoint could have been spent within. 50 type SpendHintCache interface { 51 // CommitSpendHint commits a spend hint for the outpoints to the cache. 52 CommitSpendHint(height uint32, spendRequests ...SpendRequest) error 53 54 // QuerySpendHint returns the latest spend hint for an outpoint. 55 // ErrSpendHintNotFound is returned if a spend hint does not exist 56 // within the cache for the outpoint. 57 QuerySpendHint(spendRequest SpendRequest) (uint32, error) 58 59 // PurgeSpendHint removes the spend hint for the outpoints from the 60 // cache. 61 PurgeSpendHint(spendRequests ...SpendRequest) error 62 } 63 64 // ConfirmHintCache is an interface whose duty is to cache confirm hints for 65 // transactions. A confirm hint is defined as the earliest height in the chain 66 // at which a transaction could have been included in a block. 67 type ConfirmHintCache interface { 68 // CommitConfirmHint commits a confirm hint for the transactions to the 69 // cache. 70 CommitConfirmHint(height uint32, confRequests ...ConfRequest) error 71 72 // QueryConfirmHint returns the latest confirm hint for a transaction 73 // hash. ErrConfirmHintNotFound is returned if a confirm hint does not 74 // exist within the cache for the transaction hash. 75 QueryConfirmHint(confRequest ConfRequest) (uint32, error) 76 77 // PurgeConfirmHint removes the confirm hint for the transactions from 78 // the cache. 79 PurgeConfirmHint(confRequests ...ConfRequest) error 80 } 81 82 // HeightHintCache is an implementation of the SpendHintCache and 83 // ConfirmHintCache interfaces backed by a channeldb DB instance where the hints 84 // will be stored. 85 type HeightHintCache struct { 86 cfg CacheConfig 87 db kvdb.Backend 88 } 89 90 // Compile-time checks to ensure HeightHintCache satisfies the SpendHintCache 91 // and ConfirmHintCache interfaces. 92 var _ SpendHintCache = (*HeightHintCache)(nil) 93 var _ ConfirmHintCache = (*HeightHintCache)(nil) 94 95 // NewHeightHintCache returns a new height hint cache backed by a database. 96 func NewHeightHintCache(cfg CacheConfig, db kvdb.Backend) (*HeightHintCache, 97 error) { 98 99 cache := &HeightHintCache{cfg, db} 100 if err := cache.initBuckets(); err != nil { 101 return nil, err 102 } 103 104 return cache, nil 105 } 106 107 // initBuckets ensures that the primary buckets used by the circuit are 108 // initialized so that we can assume their existence after startup. 109 func (c *HeightHintCache) initBuckets() error { 110 return kvdb.Batch(c.db, func(tx kvdb.RwTx) error { 111 _, err := tx.CreateTopLevelBucket(spendHintBucket) 112 if err != nil { 113 return err 114 } 115 116 _, err = tx.CreateTopLevelBucket(confirmHintBucket) 117 return err 118 }) 119 } 120 121 // CommitSpendHint commits a spend hint for the outpoints to the cache. 122 func (c *HeightHintCache) CommitSpendHint(height uint32, 123 spendRequests ...SpendRequest) error { 124 125 if len(spendRequests) == 0 { 126 return nil 127 } 128 129 Log.Tracef("Updating spend hint to height %d for %v", height, 130 spendRequests) 131 132 return kvdb.Batch(c.db, func(tx kvdb.RwTx) error { 133 spendHints := tx.ReadWriteBucket(spendHintBucket) 134 if spendHints == nil { 135 return ErrCorruptedHeightHintCache 136 } 137 138 var hint bytes.Buffer 139 if err := channeldb.WriteElement(&hint, height); err != nil { 140 return err 141 } 142 143 for _, spendRequest := range spendRequests { 144 spendHintKey, err := spendRequest.SpendHintKey() 145 if err != nil { 146 return err 147 } 148 err = spendHints.Put(spendHintKey, hint.Bytes()) 149 if err != nil { 150 return err 151 } 152 } 153 154 return nil 155 }) 156 } 157 158 // QuerySpendHint returns the latest spend hint for an outpoint. 159 // ErrSpendHintNotFound is returned if a spend hint does not exist within the 160 // cache for the outpoint. 161 func (c *HeightHintCache) QuerySpendHint(spendRequest SpendRequest) (uint32, error) { 162 var hint uint32 163 if c.cfg.QueryDisable { 164 Log.Debugf("Ignoring spend height hint for %v (height hint cache "+ 165 "query disabled)", spendRequest) 166 return 0, nil 167 } 168 err := kvdb.View(c.db, func(tx kvdb.RTx) error { 169 spendHints := tx.ReadBucket(spendHintBucket) 170 if spendHints == nil { 171 return ErrCorruptedHeightHintCache 172 } 173 174 spendHintKey, err := spendRequest.SpendHintKey() 175 if err != nil { 176 return err 177 } 178 spendHint := spendHints.Get(spendHintKey) 179 if spendHint == nil { 180 return ErrSpendHintNotFound 181 } 182 183 return channeldb.ReadElement(bytes.NewReader(spendHint), &hint) 184 }, func() { 185 hint = 0 186 }) 187 if err != nil { 188 return 0, err 189 } 190 191 return hint, nil 192 } 193 194 // PurgeSpendHint removes the spend hint for the outpoints from the cache. 195 func (c *HeightHintCache) PurgeSpendHint(spendRequests ...SpendRequest) error { 196 if len(spendRequests) == 0 { 197 return nil 198 } 199 200 Log.Tracef("Removing spend hints for %v", spendRequests) 201 202 return kvdb.Batch(c.db, func(tx kvdb.RwTx) error { 203 spendHints := tx.ReadWriteBucket(spendHintBucket) 204 if spendHints == nil { 205 return ErrCorruptedHeightHintCache 206 } 207 208 for _, spendRequest := range spendRequests { 209 spendHintKey, err := spendRequest.SpendHintKey() 210 if err != nil { 211 return err 212 } 213 if err := spendHints.Delete(spendHintKey); err != nil { 214 return err 215 } 216 } 217 218 return nil 219 }) 220 } 221 222 // CommitConfirmHint commits a confirm hint for the transactions to the cache. 223 func (c *HeightHintCache) CommitConfirmHint(height uint32, 224 confRequests ...ConfRequest) error { 225 226 if len(confRequests) == 0 { 227 return nil 228 } 229 230 Log.Tracef("Updating confirm hints to height %d for %v", height, 231 confRequests) 232 233 return kvdb.Batch(c.db, func(tx kvdb.RwTx) error { 234 confirmHints := tx.ReadWriteBucket(confirmHintBucket) 235 if confirmHints == nil { 236 return ErrCorruptedHeightHintCache 237 } 238 239 var hint bytes.Buffer 240 if err := channeldb.WriteElement(&hint, height); err != nil { 241 return err 242 } 243 244 for _, confRequest := range confRequests { 245 confHintKey, err := confRequest.ConfHintKey() 246 if err != nil { 247 return err 248 } 249 err = confirmHints.Put(confHintKey, hint.Bytes()) 250 if err != nil { 251 return err 252 } 253 } 254 255 return nil 256 }) 257 } 258 259 // QueryConfirmHint returns the latest confirm hint for a transaction hash. 260 // ErrConfirmHintNotFound is returned if a confirm hint does not exist within 261 // the cache for the transaction hash. 262 func (c *HeightHintCache) QueryConfirmHint(confRequest ConfRequest) (uint32, error) { 263 var hint uint32 264 if c.cfg.QueryDisable { 265 Log.Debugf("Ignoring confirmation height hint for %v (height hint "+ 266 "cache query disabled)", confRequest) 267 return 0, nil 268 } 269 err := kvdb.View(c.db, func(tx kvdb.RTx) error { 270 confirmHints := tx.ReadBucket(confirmHintBucket) 271 if confirmHints == nil { 272 return ErrCorruptedHeightHintCache 273 } 274 275 confHintKey, err := confRequest.ConfHintKey() 276 if err != nil { 277 return err 278 } 279 confirmHint := confirmHints.Get(confHintKey) 280 if confirmHint == nil { 281 return ErrConfirmHintNotFound 282 } 283 284 return channeldb.ReadElement(bytes.NewReader(confirmHint), &hint) 285 }, func() { 286 hint = 0 287 }) 288 if err != nil { 289 return 0, err 290 } 291 292 return hint, nil 293 } 294 295 // PurgeConfirmHint removes the confirm hint for the transactions from the 296 // cache. 297 func (c *HeightHintCache) PurgeConfirmHint(confRequests ...ConfRequest) error { 298 if len(confRequests) == 0 { 299 return nil 300 } 301 302 Log.Tracef("Removing confirm hints for %v", confRequests) 303 304 return kvdb.Batch(c.db, func(tx kvdb.RwTx) error { 305 confirmHints := tx.ReadWriteBucket(confirmHintBucket) 306 if confirmHints == nil { 307 return ErrCorruptedHeightHintCache 308 } 309 310 for _, confRequest := range confRequests { 311 confHintKey, err := confRequest.ConfHintKey() 312 if err != nil { 313 return err 314 } 315 if err := confirmHints.Delete(confHintKey); err != nil { 316 return err 317 } 318 } 319 320 return nil 321 }) 322 }