2022-11-18 16:23:03 +01:00
|
|
|
package channeldb
|
2018-05-21 17:47:39 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
|
2022-11-18 16:23:03 +01:00
|
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
2021-04-26 19:08:11 +02:00
|
|
|
"github.com/lightningnetwork/lnd/kvdb"
|
2018-05-21 17:47:39 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
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")
|
|
|
|
)
|
|
|
|
|
2022-02-07 13:58:28 +01:00
|
|
|
// CacheConfig contains the HeightHintCache configuration.
|
2020-07-15 04:19:33 +02:00
|
|
|
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
|
2020-06-27 03:40:00 +02:00
|
|
|
}
|
|
|
|
|
2018-05-21 17:47:39 +02:00
|
|
|
// 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 {
|
2020-07-15 04:19:33 +02:00
|
|
|
cfg CacheConfig
|
2021-08-03 09:57:27 +02:00
|
|
|
db kvdb.Backend
|
2018-05-21 17:47:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Compile-time checks to ensure HeightHintCache satisfies the SpendHintCache
|
|
|
|
// and ConfirmHintCache interfaces.
|
2022-11-18 16:23:03 +01:00
|
|
|
var _ chainntnfs.SpendHintCache = (*HeightHintCache)(nil)
|
|
|
|
var _ chainntnfs.ConfirmHintCache = (*HeightHintCache)(nil)
|
2018-05-21 17:47:39 +02:00
|
|
|
|
|
|
|
// NewHeightHintCache returns a new height hint cache backed by a database.
|
2021-08-03 09:57:27 +02:00
|
|
|
func NewHeightHintCache(cfg CacheConfig, db kvdb.Backend) (*HeightHintCache,
|
|
|
|
error) {
|
|
|
|
|
2020-06-27 03:40:00 +02:00
|
|
|
cache := &HeightHintCache{cfg, db}
|
2018-05-21 17:47:39 +02:00
|
|
|
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 {
|
2021-08-03 09:57:27 +02:00
|
|
|
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
|
2020-01-10 03:46:38 +01:00
|
|
|
_, err := tx.CreateTopLevelBucket(spendHintBucket)
|
2018-05-21 17:47:39 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-01-10 03:46:38 +01:00
|
|
|
_, err = tx.CreateTopLevelBucket(confirmHintBucket)
|
2018-05-21 17:47:39 +02:00
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// CommitSpendHint commits a spend hint for the outpoints to the cache.
|
2018-12-07 06:13:35 +01:00
|
|
|
func (c *HeightHintCache) CommitSpendHint(height uint32,
|
2022-11-18 16:23:03 +01:00
|
|
|
spendRequests ...chainntnfs.SpendRequest) error {
|
2018-12-07 06:13:35 +01:00
|
|
|
|
|
|
|
if len(spendRequests) == 0 {
|
2018-10-05 11:07:55 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-18 16:23:03 +01:00
|
|
|
log.Tracef("Updating spend hint to height %d for %v", height,
|
2018-12-07 06:13:35 +01:00
|
|
|
spendRequests)
|
2018-05-21 17:47:39 +02:00
|
|
|
|
2021-08-03 09:57:27 +02:00
|
|
|
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
|
2020-01-10 03:46:38 +01:00
|
|
|
spendHints := tx.ReadWriteBucket(spendHintBucket)
|
2018-05-21 17:47:39 +02:00
|
|
|
if spendHints == nil {
|
2022-11-18 16:23:03 +01:00
|
|
|
return chainntnfs.ErrCorruptedHeightHintCache
|
2018-05-21 17:47:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var hint bytes.Buffer
|
2022-11-18 16:23:03 +01:00
|
|
|
if err := WriteElement(&hint, height); err != nil {
|
2018-05-21 17:47:39 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-07 06:13:35 +01:00
|
|
|
for _, spendRequest := range spendRequests {
|
2022-11-18 16:23:03 +01:00
|
|
|
spendRequest := spendRequest
|
|
|
|
spendHintKey, err := spendHintKey(&spendRequest)
|
2018-05-21 17:47:39 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-12-07 06:13:35 +01:00
|
|
|
err = spendHints.Put(spendHintKey, hint.Bytes())
|
2018-05-21 17:47:39 +02:00
|
|
|
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.
|
2022-11-18 16:23:03 +01:00
|
|
|
func (c *HeightHintCache) QuerySpendHint(
|
|
|
|
spendRequest chainntnfs.SpendRequest) (uint32, error) {
|
|
|
|
|
2018-05-21 17:47:39 +02:00
|
|
|
var hint uint32
|
2020-07-15 04:19:33 +02:00
|
|
|
if c.cfg.QueryDisable {
|
2022-11-18 16:23:03 +01:00
|
|
|
log.Debugf("Ignoring spend height hint for %v (height hint "+
|
|
|
|
"cache query disabled)", spendRequest)
|
2020-06-27 03:40:00 +02:00
|
|
|
return 0, nil
|
|
|
|
}
|
2020-05-07 00:45:50 +02:00
|
|
|
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
|
2020-01-10 03:46:38 +01:00
|
|
|
spendHints := tx.ReadBucket(spendHintBucket)
|
2018-05-21 17:47:39 +02:00
|
|
|
if spendHints == nil {
|
2022-11-18 16:23:03 +01:00
|
|
|
return chainntnfs.ErrCorruptedHeightHintCache
|
2018-05-21 17:47:39 +02:00
|
|
|
}
|
|
|
|
|
2022-11-18 16:23:03 +01:00
|
|
|
spendHintKey, err := spendHintKey(&spendRequest)
|
2018-12-07 06:13:35 +01:00
|
|
|
if err != nil {
|
2018-05-21 17:47:39 +02:00
|
|
|
return err
|
|
|
|
}
|
2018-12-07 06:13:35 +01:00
|
|
|
spendHint := spendHints.Get(spendHintKey)
|
2018-05-21 17:47:39 +02:00
|
|
|
if spendHint == nil {
|
2022-11-18 16:23:03 +01:00
|
|
|
return chainntnfs.ErrSpendHintNotFound
|
2018-05-21 17:47:39 +02:00
|
|
|
}
|
|
|
|
|
2022-11-18 16:23:03 +01:00
|
|
|
return ReadElement(bytes.NewReader(spendHint), &hint)
|
2020-10-20 16:18:40 +02:00
|
|
|
}, func() {
|
|
|
|
hint = 0
|
2018-05-21 17:47:39 +02:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return hint, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PurgeSpendHint removes the spend hint for the outpoints from the cache.
|
2022-11-18 16:23:03 +01:00
|
|
|
func (c *HeightHintCache) PurgeSpendHint(
|
|
|
|
spendRequests ...chainntnfs.SpendRequest) error {
|
|
|
|
|
2018-12-07 06:13:35 +01:00
|
|
|
if len(spendRequests) == 0 {
|
2018-10-05 11:07:55 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-18 16:23:03 +01:00
|
|
|
log.Tracef("Removing spend hints for %v", spendRequests)
|
2018-05-21 17:47:39 +02:00
|
|
|
|
2021-08-03 09:57:27 +02:00
|
|
|
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
|
2020-01-10 03:46:38 +01:00
|
|
|
spendHints := tx.ReadWriteBucket(spendHintBucket)
|
2018-05-21 17:47:39 +02:00
|
|
|
if spendHints == nil {
|
2022-11-18 16:23:03 +01:00
|
|
|
return chainntnfs.ErrCorruptedHeightHintCache
|
2018-05-21 17:47:39 +02:00
|
|
|
}
|
|
|
|
|
2018-12-07 06:13:35 +01:00
|
|
|
for _, spendRequest := range spendRequests {
|
2022-11-18 16:23:03 +01:00
|
|
|
spendRequest := spendRequest
|
|
|
|
spendHintKey, err := spendHintKey(&spendRequest)
|
2018-05-21 17:47:39 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-12-07 06:13:35 +01:00
|
|
|
if err := spendHints.Delete(spendHintKey); err != nil {
|
2018-05-21 17:47:39 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// CommitConfirmHint commits a confirm hint for the transactions to the cache.
|
2018-12-07 06:13:35 +01:00
|
|
|
func (c *HeightHintCache) CommitConfirmHint(height uint32,
|
2022-11-18 16:23:03 +01:00
|
|
|
confRequests ...chainntnfs.ConfRequest) error {
|
2018-12-07 06:13:35 +01:00
|
|
|
|
|
|
|
if len(confRequests) == 0 {
|
2018-10-05 11:07:55 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-18 16:23:03 +01:00
|
|
|
log.Tracef("Updating confirm hints to height %d for %v", height,
|
2018-12-07 06:13:35 +01:00
|
|
|
confRequests)
|
2018-05-21 17:47:39 +02:00
|
|
|
|
2021-08-03 09:57:27 +02:00
|
|
|
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
|
2020-01-10 03:46:38 +01:00
|
|
|
confirmHints := tx.ReadWriteBucket(confirmHintBucket)
|
2018-05-21 17:47:39 +02:00
|
|
|
if confirmHints == nil {
|
2022-11-18 16:23:03 +01:00
|
|
|
return chainntnfs.ErrCorruptedHeightHintCache
|
2018-05-21 17:47:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var hint bytes.Buffer
|
2022-11-18 16:23:03 +01:00
|
|
|
if err := WriteElement(&hint, height); err != nil {
|
2018-05-21 17:47:39 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-07 06:13:35 +01:00
|
|
|
for _, confRequest := range confRequests {
|
2022-11-18 16:23:03 +01:00
|
|
|
confRequest := confRequest
|
|
|
|
confHintKey, err := confHintKey(&confRequest)
|
2018-05-21 17:47:39 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-12-07 06:13:35 +01:00
|
|
|
err = confirmHints.Put(confHintKey, hint.Bytes())
|
2018-05-21 17:47:39 +02:00
|
|
|
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.
|
2022-11-18 16:23:03 +01:00
|
|
|
func (c *HeightHintCache) QueryConfirmHint(
|
|
|
|
confRequest chainntnfs.ConfRequest) (uint32, error) {
|
|
|
|
|
2018-05-21 17:47:39 +02:00
|
|
|
var hint uint32
|
2020-07-15 04:19:33 +02:00
|
|
|
if c.cfg.QueryDisable {
|
2022-11-18 16:23:03 +01:00
|
|
|
log.Debugf("Ignoring confirmation height hint for %v (height "+
|
|
|
|
"hint cache query disabled)", confRequest)
|
2020-06-27 03:40:00 +02:00
|
|
|
return 0, nil
|
|
|
|
}
|
2020-05-07 00:45:50 +02:00
|
|
|
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
|
2020-01-10 03:46:38 +01:00
|
|
|
confirmHints := tx.ReadBucket(confirmHintBucket)
|
2018-05-21 17:47:39 +02:00
|
|
|
if confirmHints == nil {
|
2022-11-18 16:23:03 +01:00
|
|
|
return chainntnfs.ErrCorruptedHeightHintCache
|
2018-05-21 17:47:39 +02:00
|
|
|
}
|
|
|
|
|
2022-11-18 16:23:03 +01:00
|
|
|
confHintKey, err := confHintKey(&confRequest)
|
2018-12-07 06:13:35 +01:00
|
|
|
if err != nil {
|
2018-05-21 17:47:39 +02:00
|
|
|
return err
|
|
|
|
}
|
2018-12-07 06:13:35 +01:00
|
|
|
confirmHint := confirmHints.Get(confHintKey)
|
2018-05-21 17:47:39 +02:00
|
|
|
if confirmHint == nil {
|
2022-11-18 16:23:03 +01:00
|
|
|
return chainntnfs.ErrConfirmHintNotFound
|
2018-05-21 17:47:39 +02:00
|
|
|
}
|
|
|
|
|
2022-11-18 16:23:03 +01:00
|
|
|
return ReadElement(bytes.NewReader(confirmHint), &hint)
|
2020-10-20 16:18:40 +02:00
|
|
|
}, func() {
|
|
|
|
hint = 0
|
2018-05-21 17:47:39 +02:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return hint, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PurgeConfirmHint removes the confirm hint for the transactions from the
|
|
|
|
// cache.
|
2022-11-18 16:23:03 +01:00
|
|
|
func (c *HeightHintCache) PurgeConfirmHint(
|
|
|
|
confRequests ...chainntnfs.ConfRequest) error {
|
|
|
|
|
2018-12-07 06:13:35 +01:00
|
|
|
if len(confRequests) == 0 {
|
2018-10-05 11:07:55 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-18 16:23:03 +01:00
|
|
|
log.Tracef("Removing confirm hints for %v", confRequests)
|
2018-05-21 17:47:39 +02:00
|
|
|
|
2021-08-03 09:57:27 +02:00
|
|
|
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
|
2020-01-10 03:46:38 +01:00
|
|
|
confirmHints := tx.ReadWriteBucket(confirmHintBucket)
|
2018-05-21 17:47:39 +02:00
|
|
|
if confirmHints == nil {
|
2022-11-18 16:23:03 +01:00
|
|
|
return chainntnfs.ErrCorruptedHeightHintCache
|
2018-05-21 17:47:39 +02:00
|
|
|
}
|
|
|
|
|
2018-12-07 06:13:35 +01:00
|
|
|
for _, confRequest := range confRequests {
|
2022-11-18 16:23:03 +01:00
|
|
|
confRequest := confRequest
|
|
|
|
confHintKey, err := confHintKey(&confRequest)
|
2018-05-21 17:47:39 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-12-07 06:13:35 +01:00
|
|
|
if err := confirmHints.Delete(confHintKey); err != nil {
|
2018-05-21 17:47:39 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
2022-11-18 16:23:03 +01:00
|
|
|
|
|
|
|
// confHintKey returns the key that will be used to index the confirmation
|
|
|
|
// request's hint within the height hint cache.
|
|
|
|
func confHintKey(r *chainntnfs.ConfRequest) ([]byte, error) {
|
|
|
|
if r.TxID == chainntnfs.ZeroHash {
|
|
|
|
return r.PkScript.Script(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var txid bytes.Buffer
|
|
|
|
if err := WriteElement(&txid, r.TxID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return txid.Bytes(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// spendHintKey returns the key that will be used to index the spend request's
|
|
|
|
// hint within the height hint cache.
|
|
|
|
func spendHintKey(r *chainntnfs.SpendRequest) ([]byte, error) {
|
|
|
|
if r.OutPoint == chainntnfs.ZeroOutPoint {
|
|
|
|
return r.PkScript.Script(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var outpoint bytes.Buffer
|
|
|
|
err := WriteElement(&outpoint, r.OutPoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return outpoint.Bytes(), nil
|
|
|
|
}
|