lnd.xprv/chainntnfs/height_hint_cache.go
Andras Banki-Horvath 2a358327f4
multi: add reset closure to kvdb.View
This commit adds a reset() closure to the kvdb.View function which will
be called before each retry (including the first) of the view
transaction. The reset() closure can be used to reset external state
(eg slices or maps) where the view closure puts intermediate results.
2020-11-05 17:57:12 +01:00

321 lines
9.1 KiB
Go

package chainntnfs
import (
"bytes"
"errors"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
)
var (
// spendHintBucket is the name of the bucket which houses the height
// hint for outpoints. Each height hint represents the earliest height
// at which its corresponding outpoint could have been spent within.
spendHintBucket = []byte("spend-hints")
// confirmHintBucket is the name of the bucket which houses the height
// hints for transactions. Each height hint represents the earliest
// height at which its corresponding transaction could have been
// confirmed within.
confirmHintBucket = []byte("confirm-hints")
// ErrCorruptedHeightHintCache indicates that the on-disk bucketing
// structure has altered since the height hint cache instance was
// initialized.
ErrCorruptedHeightHintCache = errors.New("height hint cache has been " +
"corrupted")
// ErrSpendHintNotFound is an error returned when a spend hint for an
// outpoint was not found.
ErrSpendHintNotFound = errors.New("spend hint not found")
// ErrConfirmHintNotFound is an error returned when a confirm hint for a
// transaction was not found.
ErrConfirmHintNotFound = errors.New("confirm hint not found")
)
// CacheConfig contains the HeightHintCache configuration
type CacheConfig struct {
// QueryDisable prevents reliance on the Height Hint Cache. This is
// necessary to recover from an edge case when the height recorded in
// the cache is higher than the actual height of a spend, causing a
// channel to become "stuck" in a pending close state.
QueryDisable bool
}
// SpendHintCache is an interface whose duty is to cache spend hints for
// outpoints. A spend hint is defined as the earliest height in the chain at
// which an outpoint could have been spent within.
type SpendHintCache interface {
// CommitSpendHint commits a spend hint for the outpoints to the cache.
CommitSpendHint(height uint32, spendRequests ...SpendRequest) error
// QuerySpendHint returns the latest spend hint for an outpoint.
// ErrSpendHintNotFound is returned if a spend hint does not exist
// within the cache for the outpoint.
QuerySpendHint(spendRequest SpendRequest) (uint32, error)
// PurgeSpendHint removes the spend hint for the outpoints from the
// cache.
PurgeSpendHint(spendRequests ...SpendRequest) error
}
// ConfirmHintCache is an interface whose duty is to cache confirm hints for
// transactions. A confirm hint is defined as the earliest height in the chain
// at which a transaction could have been included in a block.
type ConfirmHintCache interface {
// CommitConfirmHint commits a confirm hint for the transactions to the
// cache.
CommitConfirmHint(height uint32, confRequests ...ConfRequest) error
// QueryConfirmHint returns the latest confirm hint for a transaction
// hash. ErrConfirmHintNotFound is returned if a confirm hint does not
// exist within the cache for the transaction hash.
QueryConfirmHint(confRequest ConfRequest) (uint32, error)
// PurgeConfirmHint removes the confirm hint for the transactions from
// the cache.
PurgeConfirmHint(confRequests ...ConfRequest) error
}
// HeightHintCache is an implementation of the SpendHintCache and
// ConfirmHintCache interfaces backed by a channeldb DB instance where the hints
// will be stored.
type HeightHintCache struct {
cfg CacheConfig
db *channeldb.DB
}
// Compile-time checks to ensure HeightHintCache satisfies the SpendHintCache
// and ConfirmHintCache interfaces.
var _ SpendHintCache = (*HeightHintCache)(nil)
var _ ConfirmHintCache = (*HeightHintCache)(nil)
// NewHeightHintCache returns a new height hint cache backed by a database.
func NewHeightHintCache(cfg CacheConfig, db *channeldb.DB) (*HeightHintCache, error) {
cache := &HeightHintCache{cfg, db}
if err := cache.initBuckets(); err != nil {
return nil, err
}
return cache, nil
}
// initBuckets ensures that the primary buckets used by the circuit are
// initialized so that we can assume their existence after startup.
func (c *HeightHintCache) initBuckets() error {
return kvdb.Batch(c.db.Backend, func(tx kvdb.RwTx) error {
_, err := tx.CreateTopLevelBucket(spendHintBucket)
if err != nil {
return err
}
_, err = tx.CreateTopLevelBucket(confirmHintBucket)
return err
})
}
// CommitSpendHint commits a spend hint for the outpoints to the cache.
func (c *HeightHintCache) CommitSpendHint(height uint32,
spendRequests ...SpendRequest) error {
if len(spendRequests) == 0 {
return nil
}
Log.Tracef("Updating spend hint to height %d for %v", height,
spendRequests)
return kvdb.Batch(c.db.Backend, func(tx kvdb.RwTx) error {
spendHints := tx.ReadWriteBucket(spendHintBucket)
if spendHints == nil {
return ErrCorruptedHeightHintCache
}
var hint bytes.Buffer
if err := channeldb.WriteElement(&hint, height); err != nil {
return err
}
for _, spendRequest := range spendRequests {
spendHintKey, err := spendRequest.SpendHintKey()
if err != nil {
return err
}
err = spendHints.Put(spendHintKey, hint.Bytes())
if err != nil {
return err
}
}
return nil
})
}
// QuerySpendHint returns the latest spend hint for an outpoint.
// ErrSpendHintNotFound is returned if a spend hint does not exist within the
// cache for the outpoint.
func (c *HeightHintCache) QuerySpendHint(spendRequest SpendRequest) (uint32, error) {
var hint uint32
if c.cfg.QueryDisable {
Log.Debugf("Ignoring spend height hint for %v (height hint cache "+
"query disabled)", spendRequest)
return 0, nil
}
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
spendHints := tx.ReadBucket(spendHintBucket)
if spendHints == nil {
return ErrCorruptedHeightHintCache
}
spendHintKey, err := spendRequest.SpendHintKey()
if err != nil {
return err
}
spendHint := spendHints.Get(spendHintKey)
if spendHint == nil {
return ErrSpendHintNotFound
}
return channeldb.ReadElement(bytes.NewReader(spendHint), &hint)
}, func() {
hint = 0
})
if err != nil {
return 0, err
}
return hint, nil
}
// PurgeSpendHint removes the spend hint for the outpoints from the cache.
func (c *HeightHintCache) PurgeSpendHint(spendRequests ...SpendRequest) error {
if len(spendRequests) == 0 {
return nil
}
Log.Tracef("Removing spend hints for %v", spendRequests)
return kvdb.Batch(c.db.Backend, func(tx kvdb.RwTx) error {
spendHints := tx.ReadWriteBucket(spendHintBucket)
if spendHints == nil {
return ErrCorruptedHeightHintCache
}
for _, spendRequest := range spendRequests {
spendHintKey, err := spendRequest.SpendHintKey()
if err != nil {
return err
}
if err := spendHints.Delete(spendHintKey); err != nil {
return err
}
}
return nil
})
}
// CommitConfirmHint commits a confirm hint for the transactions to the cache.
func (c *HeightHintCache) CommitConfirmHint(height uint32,
confRequests ...ConfRequest) error {
if len(confRequests) == 0 {
return nil
}
Log.Tracef("Updating confirm hints to height %d for %v", height,
confRequests)
return kvdb.Batch(c.db.Backend, func(tx kvdb.RwTx) error {
confirmHints := tx.ReadWriteBucket(confirmHintBucket)
if confirmHints == nil {
return ErrCorruptedHeightHintCache
}
var hint bytes.Buffer
if err := channeldb.WriteElement(&hint, height); err != nil {
return err
}
for _, confRequest := range confRequests {
confHintKey, err := confRequest.ConfHintKey()
if err != nil {
return err
}
err = confirmHints.Put(confHintKey, hint.Bytes())
if err != nil {
return err
}
}
return nil
})
}
// QueryConfirmHint returns the latest confirm hint for a transaction hash.
// ErrConfirmHintNotFound is returned if a confirm hint does not exist within
// the cache for the transaction hash.
func (c *HeightHintCache) QueryConfirmHint(confRequest ConfRequest) (uint32, error) {
var hint uint32
if c.cfg.QueryDisable {
Log.Debugf("Ignoring confirmation height hint for %v (height hint "+
"cache query disabled)", confRequest)
return 0, nil
}
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
confirmHints := tx.ReadBucket(confirmHintBucket)
if confirmHints == nil {
return ErrCorruptedHeightHintCache
}
confHintKey, err := confRequest.ConfHintKey()
if err != nil {
return err
}
confirmHint := confirmHints.Get(confHintKey)
if confirmHint == nil {
return ErrConfirmHintNotFound
}
return channeldb.ReadElement(bytes.NewReader(confirmHint), &hint)
}, func() {
hint = 0
})
if err != nil {
return 0, err
}
return hint, nil
}
// PurgeConfirmHint removes the confirm hint for the transactions from the
// cache.
func (c *HeightHintCache) PurgeConfirmHint(confRequests ...ConfRequest) error {
if len(confRequests) == 0 {
return nil
}
Log.Tracef("Removing confirm hints for %v", confRequests)
return kvdb.Batch(c.db.Backend, func(tx kvdb.RwTx) error {
confirmHints := tx.ReadWriteBucket(confirmHintBucket)
if confirmHints == nil {
return ErrCorruptedHeightHintCache
}
for _, confRequest := range confRequests {
confHintKey, err := confRequest.ConfHintKey()
if err != nil {
return err
}
if err := confirmHints.Delete(confHintKey); err != nil {
return err
}
}
return nil
})
}