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  }