Merge pull request #7215 from positiveblue/invoices-channeldb-dep
Invoices: invert package dependency with `channeldb`
This commit is contained in:
57 files changed
+3319
-2637
No files matched your search
+5
-77
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
@@ -177,11 +178,6 @@ var (
|
||||
// tolerant.
|
||||
ErrNoPendingCommit = fmt.Errorf("no pending commits found")
|
||||
|
||||
// ErrInvalidCircuitKeyLen signals that a circuit key could not be
|
||||
// decoded because the byte slice is of an invalid length.
|
||||
ErrInvalidCircuitKeyLen = fmt.Errorf(
|
||||
"length of serialized circuit key must be 16 bytes")
|
||||
|
||||
// ErrNoCommitPoint is returned when no data loss commit point is found
|
||||
// in the database.
|
||||
ErrNoCommitPoint = fmt.Errorf("no commit point found")
|
||||
@@ -2156,74 +2152,6 @@ func deserializeLogUpdate(r io.Reader) (*LogUpdate, error) {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// CircuitKey is used by a channel to uniquely identify the HTLCs it receives
|
||||
// from the switch, and is used to purge our in-memory state of HTLCs that have
|
||||
// already been processed by a link. Two list of CircuitKeys are included in
|
||||
// each CommitDiff to allow a link to determine which in-memory htlcs directed
|
||||
// the opening and closing of circuits in the switch's circuit map.
|
||||
type CircuitKey struct {
|
||||
// ChanID is the short chanid indicating the HTLC's origin.
|
||||
//
|
||||
// NOTE: It is fine for this value to be blank, as this indicates a
|
||||
// locally-sourced payment.
|
||||
ChanID lnwire.ShortChannelID
|
||||
|
||||
// HtlcID is the unique htlc index predominately assigned by links,
|
||||
// though can also be assigned by switch in the case of locally-sourced
|
||||
// payments.
|
||||
HtlcID uint64
|
||||
}
|
||||
|
||||
// SetBytes deserializes the given bytes into this CircuitKey.
|
||||
func (k *CircuitKey) SetBytes(bs []byte) error {
|
||||
if len(bs) != 16 {
|
||||
return ErrInvalidCircuitKeyLen
|
||||
}
|
||||
|
||||
k.ChanID = lnwire.NewShortChanIDFromInt(
|
||||
binary.BigEndian.Uint64(bs[:8]))
|
||||
k.HtlcID = binary.BigEndian.Uint64(bs[8:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bytes returns the serialized bytes for this circuit key.
|
||||
func (k CircuitKey) Bytes() []byte {
|
||||
bs := make([]byte, 16)
|
||||
binary.BigEndian.PutUint64(bs[:8], k.ChanID.ToUint64())
|
||||
binary.BigEndian.PutUint64(bs[8:], k.HtlcID)
|
||||
return bs
|
||||
}
|
||||
|
||||
// Encode writes a CircuitKey to the provided io.Writer.
|
||||
func (k *CircuitKey) Encode(w io.Writer) error {
|
||||
var scratch [16]byte
|
||||
binary.BigEndian.PutUint64(scratch[:8], k.ChanID.ToUint64())
|
||||
binary.BigEndian.PutUint64(scratch[8:], k.HtlcID)
|
||||
|
||||
_, err := w.Write(scratch[:])
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode reads a CircuitKey from the provided io.Reader.
|
||||
func (k *CircuitKey) Decode(r io.Reader) error {
|
||||
var scratch [16]byte
|
||||
|
||||
if _, err := io.ReadFull(r, scratch[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
k.ChanID = lnwire.NewShortChanIDFromInt(
|
||||
binary.BigEndian.Uint64(scratch[:8]))
|
||||
k.HtlcID = binary.BigEndian.Uint64(scratch[8:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a string representation of the CircuitKey.
|
||||
func (k CircuitKey) String() string {
|
||||
return fmt.Sprintf("(Chan ID=%s, HTLC ID=%d)", k.ChanID, k.HtlcID)
|
||||
}
|
||||
|
||||
// CommitDiff represents the delta needed to apply the state transition between
|
||||
// two subsequent commitment states. Given state N and state N+1, one is able
|
||||
// to apply the set of messages contained within the CommitDiff to N to arrive
|
||||
@@ -2252,13 +2180,13 @@ type CommitDiff struct {
|
||||
// Add packets included in this commitment txn. After a restart, this
|
||||
// set of htlcs is acked from the link's incoming mailbox to ensure
|
||||
// there isn't an attempt to re-add them to this commitment txn.
|
||||
OpenedCircuitKeys []CircuitKey
|
||||
OpenedCircuitKeys []models.CircuitKey
|
||||
|
||||
// ClosedCircuitKeys records the unique identifiers for any settle/fail
|
||||
// packets that were resolved by this commitment txn. After a restart,
|
||||
// this is used to ensure those circuits are removed from the circuit
|
||||
// map, and the downstream packets in the link's mailbox are removed.
|
||||
ClosedCircuitKeys []CircuitKey
|
||||
ClosedCircuitKeys []models.CircuitKey
|
||||
|
||||
// AddAcks specifies the locations (commit height, pkg index) of any
|
||||
// Adds that were failed/settled in this commit diff. This will ack
|
||||
@@ -2387,7 +2315,7 @@ func deserializeCommitDiff(r io.Reader) (*CommitDiff, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.OpenedCircuitKeys = make([]CircuitKey, numOpenRefs)
|
||||
d.OpenedCircuitKeys = make([]models.CircuitKey, numOpenRefs)
|
||||
for i := 0; i < int(numOpenRefs); i++ {
|
||||
err := ReadElements(r,
|
||||
&d.OpenedCircuitKeys[i].ChanID,
|
||||
@@ -2402,7 +2330,7 @@ func deserializeCommitDiff(r io.Reader) (*CommitDiff, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.ClosedCircuitKeys = make([]CircuitKey, numClosedRefs)
|
||||
d.ClosedCircuitKeys = make([]models.CircuitKey, numClosedRefs)
|
||||
for i := 0; i < int(numClosedRefs); i++ {
|
||||
err := ReadElements(r,
|
||||
&d.ClosedCircuitKeys[i].ChanID,
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
_ "github.com/btcsuite/btcwallet/walletdb/bdb"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
@@ -716,8 +717,8 @@ func TestChannelStateTransition(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
OpenedCircuitKeys: []CircuitKey{},
|
||||
ClosedCircuitKeys: []CircuitKey{},
|
||||
OpenedCircuitKeys: []models.CircuitKey{},
|
||||
ClosedCircuitKeys: []models.CircuitKey{},
|
||||
}
|
||||
copy(commitDiff.LogUpdates[0].UpdateMsg.(*lnwire.UpdateAddHTLC).PaymentHash[:],
|
||||
bytes.Repeat([]byte{1}, 32))
|
||||
|
||||
@@ -31,30 +31,6 @@ var (
|
||||
// created.
|
||||
ErrNoPastDeltas = fmt.Errorf("channel has no recorded deltas")
|
||||
|
||||
// ErrInvoiceNotFound is returned when a targeted invoice can't be
|
||||
// found.
|
||||
ErrInvoiceNotFound = fmt.Errorf("unable to locate invoice")
|
||||
|
||||
// ErrNoInvoicesCreated is returned when we don't have invoices in
|
||||
// our database to return.
|
||||
ErrNoInvoicesCreated = fmt.Errorf("there are no existing invoices")
|
||||
|
||||
// ErrDuplicateInvoice is returned when an invoice with the target
|
||||
// payment hash already exists.
|
||||
ErrDuplicateInvoice = fmt.Errorf("invoice with payment hash already exists")
|
||||
|
||||
// ErrDuplicatePayAddr is returned when an invoice with the target
|
||||
// payment addr already exists.
|
||||
ErrDuplicatePayAddr = fmt.Errorf("invoice with payemnt addr already exists")
|
||||
|
||||
// ErrInvRefEquivocation is returned when an InvoiceRef targets
|
||||
// multiple, distinct invoices.
|
||||
ErrInvRefEquivocation = errors.New("inv ref matches multiple invoices")
|
||||
|
||||
// ErrNoPaymentsCreated is returned when bucket of payments hasn't been
|
||||
// created.
|
||||
ErrNoPaymentsCreated = fmt.Errorf("there are no existing payments")
|
||||
|
||||
// ErrNodeNotFound is returned when node bucket exists, but node with
|
||||
// specific identity can't be found.
|
||||
ErrNodeNotFound = fmt.Errorf("link node with target identity not found")
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/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")
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 kvdb.Backend
|
||||
}
|
||||
|
||||
// Compile-time checks to ensure HeightHintCache satisfies the SpendHintCache
|
||||
// and ConfirmHintCache interfaces.
|
||||
var _ chainntnfs.SpendHintCache = (*HeightHintCache)(nil)
|
||||
var _ chainntnfs.ConfirmHintCache = (*HeightHintCache)(nil)
|
||||
|
||||
// NewHeightHintCache returns a new height hint cache backed by a database.
|
||||
func NewHeightHintCache(cfg CacheConfig, db kvdb.Backend) (*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, 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 ...chainntnfs.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, func(tx kvdb.RwTx) error {
|
||||
spendHints := tx.ReadWriteBucket(spendHintBucket)
|
||||
if spendHints == nil {
|
||||
return chainntnfs.ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
var hint bytes.Buffer
|
||||
if err := WriteElement(&hint, height); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, spendRequest := range spendRequests {
|
||||
spendRequest := spendRequest
|
||||
spendHintKey, err := spendHintKey(&spendRequest)
|
||||
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 chainntnfs.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 chainntnfs.ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
spendHintKey, err := spendHintKey(&spendRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spendHint := spendHints.Get(spendHintKey)
|
||||
if spendHint == nil {
|
||||
return chainntnfs.ErrSpendHintNotFound
|
||||
}
|
||||
|
||||
return 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 ...chainntnfs.SpendRequest) error {
|
||||
|
||||
if len(spendRequests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Tracef("Removing spend hints for %v", spendRequests)
|
||||
|
||||
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
|
||||
spendHints := tx.ReadWriteBucket(spendHintBucket)
|
||||
if spendHints == nil {
|
||||
return chainntnfs.ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
for _, spendRequest := range spendRequests {
|
||||
spendRequest := spendRequest
|
||||
spendHintKey, err := spendHintKey(&spendRequest)
|
||||
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 ...chainntnfs.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, func(tx kvdb.RwTx) error {
|
||||
confirmHints := tx.ReadWriteBucket(confirmHintBucket)
|
||||
if confirmHints == nil {
|
||||
return chainntnfs.ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
var hint bytes.Buffer
|
||||
if err := WriteElement(&hint, height); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, confRequest := range confRequests {
|
||||
confRequest := confRequest
|
||||
confHintKey, err := confHintKey(&confRequest)
|
||||
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 chainntnfs.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 chainntnfs.ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
confHintKey, err := confHintKey(&confRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confirmHint := confirmHints.Get(confHintKey)
|
||||
if confirmHint == nil {
|
||||
return chainntnfs.ErrConfirmHintNotFound
|
||||
}
|
||||
|
||||
return 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 ...chainntnfs.ConfRequest) error {
|
||||
|
||||
if len(confRequests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Tracef("Removing confirm hints for %v", confRequests)
|
||||
|
||||
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
|
||||
confirmHints := tx.ReadWriteBucket(confirmHintBucket)
|
||||
if confirmHints == nil {
|
||||
return chainntnfs.ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
for _, confRequest := range confRequests {
|
||||
confRequest := confRequest
|
||||
confHintKey, err := confHintKey(&confRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := confirmHints.Delete(confHintKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func initHintCache(t *testing.T) *HeightHintCache {
|
||||
t.Helper()
|
||||
|
||||
defaultCfg := CacheConfig{
|
||||
QueryDisable: false,
|
||||
}
|
||||
|
||||
return initHintCacheWithConfig(t, defaultCfg)
|
||||
}
|
||||
|
||||
func initHintCacheWithConfig(t *testing.T, cfg CacheConfig) *HeightHintCache {
|
||||
t.Helper()
|
||||
|
||||
db, err := Open(t.TempDir())
|
||||
require.NoError(t, err, "unable to create db")
|
||||
hintCache, err := NewHeightHintCache(cfg, db.Backend)
|
||||
require.NoError(t, err, "unable to create hint cache")
|
||||
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, db.Close())
|
||||
})
|
||||
|
||||
return hintCache
|
||||
}
|
||||
|
||||
// TestHeightHintCacheConfirms ensures that the height hint cache properly
|
||||
// caches confirm hints for transactions.
|
||||
func TestHeightHintCacheConfirms(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hintCache := initHintCache(t)
|
||||
|
||||
// Querying for a transaction hash not found within the cache should
|
||||
// return an error indication so.
|
||||
var unknownHash chainhash.Hash
|
||||
copy(unknownHash[:], bytes.Repeat([]byte{0x01}, 32))
|
||||
unknownConfRequest := chainntnfs.ConfRequest{TxID: unknownHash}
|
||||
_, err := hintCache.QueryConfirmHint(unknownConfRequest)
|
||||
require.ErrorIs(t, err, chainntnfs.ErrConfirmHintNotFound)
|
||||
|
||||
// Now, we'll create some transaction hashes and commit them to the
|
||||
// cache with the same confirm hint.
|
||||
const height = 100
|
||||
const numHashes = 5
|
||||
confRequests := make([]chainntnfs.ConfRequest, numHashes)
|
||||
for i := 0; i < numHashes; i++ {
|
||||
var txHash chainhash.Hash
|
||||
copy(txHash[:], bytes.Repeat([]byte{byte(i + 1)}, 32))
|
||||
confRequests[i] = chainntnfs.ConfRequest{TxID: txHash}
|
||||
}
|
||||
|
||||
err = hintCache.CommitConfirmHint(height, confRequests...)
|
||||
require.NoError(t, err, "unable to add entries to cache")
|
||||
|
||||
// With the hashes committed, we'll now query the cache to ensure that
|
||||
// we're able to properly retrieve the confirm hints.
|
||||
for _, confRequest := range confRequests {
|
||||
confirmHint, err := hintCache.QueryConfirmHint(confRequest)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, height, confirmHint)
|
||||
}
|
||||
|
||||
// We'll also attempt to purge all of them in a single database
|
||||
// transaction.
|
||||
err = hintCache.PurgeConfirmHint(confRequests...)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Finally, we'll attempt to query for each hash. We should expect not
|
||||
// to find a hint for any of them.
|
||||
for _, confRequest := range confRequests {
|
||||
_, err := hintCache.QueryConfirmHint(confRequest)
|
||||
require.ErrorIs(t, err, chainntnfs.ErrConfirmHintNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHeightHintCacheSpends ensures that the height hint cache properly caches
|
||||
// spend hints for outpoints.
|
||||
func TestHeightHintCacheSpends(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hintCache := initHintCache(t)
|
||||
|
||||
// Querying for an outpoint not found within the cache should return an
|
||||
// error indication so.
|
||||
unknownOutPoint := wire.OutPoint{Index: 1}
|
||||
unknownSpendRequest := chainntnfs.SpendRequest{
|
||||
OutPoint: unknownOutPoint,
|
||||
}
|
||||
_, err := hintCache.QuerySpendHint(unknownSpendRequest)
|
||||
require.ErrorIs(t, err, chainntnfs.ErrSpendHintNotFound)
|
||||
|
||||
// Now, we'll create some outpoints and commit them to the cache with
|
||||
// the same spend hint.
|
||||
const height = 100
|
||||
const numOutpoints = 5
|
||||
spendRequests := make([]chainntnfs.SpendRequest, numOutpoints)
|
||||
for i := uint32(0); i < numOutpoints; i++ {
|
||||
spendRequests[i] = chainntnfs.SpendRequest{
|
||||
OutPoint: wire.OutPoint{Index: i + 1},
|
||||
}
|
||||
}
|
||||
|
||||
err = hintCache.CommitSpendHint(height, spendRequests...)
|
||||
require.NoError(t, err, "unable to add entries to cache")
|
||||
|
||||
// With the outpoints committed, we'll now query the cache to ensure
|
||||
// that we're able to properly retrieve the confirm hints.
|
||||
for _, spendRequest := range spendRequests {
|
||||
spendHint, err := hintCache.QuerySpendHint(spendRequest)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, height, spendHint)
|
||||
}
|
||||
|
||||
// We'll also attempt to purge all of them in a single database
|
||||
// transaction.
|
||||
err = hintCache.PurgeSpendHint(spendRequests...)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Finally, we'll attempt to query for each outpoint. We should expect
|
||||
// not to find a hint for any of them.
|
||||
for _, spendRequest := range spendRequests {
|
||||
_, err = hintCache.QuerySpendHint(spendRequest)
|
||||
require.ErrorIs(t, err, chainntnfs.ErrSpendHintNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// TestQueryDisable asserts querying for confirmation or spend hints always
|
||||
// return height zero when QueryDisabled is set to true in the CacheConfig.
|
||||
func TestQueryDisable(t *testing.T) {
|
||||
cfg := CacheConfig{
|
||||
QueryDisable: true,
|
||||
}
|
||||
|
||||
hintCache := initHintCacheWithConfig(t, cfg)
|
||||
|
||||
// Insert a new confirmation hint with a non-zero height.
|
||||
const confHeight = 100
|
||||
confRequest := chainntnfs.ConfRequest{
|
||||
TxID: chainhash.Hash{0x01, 0x02, 0x03},
|
||||
}
|
||||
err := hintCache.CommitConfirmHint(confHeight, confRequest)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Query for the confirmation hint, which should return zero.
|
||||
cachedConfHeight, err := hintCache.QueryConfirmHint(confRequest)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, uint32(0), cachedConfHeight)
|
||||
|
||||
// Insert a new spend hint with a non-zero height.
|
||||
const spendHeight = 200
|
||||
spendRequest := chainntnfs.SpendRequest{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{0x4, 0x05, 0x06},
|
||||
Index: 42,
|
||||
},
|
||||
}
|
||||
err = hintCache.CommitSpendHint(spendHeight, spendRequest)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Query for the spend hint, which should return zero.
|
||||
cachedSpendHeight, err := hintCache.QuerySpendHint(spendRequest)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, uint32(0), cachedSpendHeight)
|
||||
}
|
||||
+615
-519
File diff suppressed because it is too large.
Load diff
+307
-1131
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,92 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
const (
|
||||
// serializedCircuitKeyLen is the exact length needed to encode a
|
||||
// serialized CircuitKey.
|
||||
serializedCircuitKeyLen = 16
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidCircuitKeyLen signals that a circuit key could not be
|
||||
// decoded because the byte slice is of an invalid length.
|
||||
ErrInvalidCircuitKeyLen = fmt.Errorf("length of serialized circuit " +
|
||||
"key must be 16 bytes")
|
||||
)
|
||||
|
||||
// CircuitKey is used by a channel to uniquely identify the HTLCs it receives
|
||||
// from the switch, and is used to purge our in-memory state of HTLCs that have
|
||||
// already been processed by a link. Two list of CircuitKeys are included in
|
||||
// each CommitDiff to allow a link to determine which in-memory htlcs directed
|
||||
// the opening and closing of circuits in the switch's circuit map.
|
||||
type CircuitKey struct {
|
||||
// ChanID is the short chanid indicating the HTLC's origin.
|
||||
//
|
||||
// NOTE: It is fine for this value to be blank, as this indicates a
|
||||
// locally-sourced payment.
|
||||
ChanID lnwire.ShortChannelID
|
||||
|
||||
// HtlcID is the unique htlc index predominately assigned by links,
|
||||
// though can also be assigned by switch in the case of locally-sourced
|
||||
// payments.
|
||||
HtlcID uint64
|
||||
}
|
||||
|
||||
// SetBytes deserializes the given bytes into this CircuitKey.
|
||||
func (k *CircuitKey) SetBytes(bs []byte) error {
|
||||
if len(bs) != serializedCircuitKeyLen {
|
||||
return ErrInvalidCircuitKeyLen
|
||||
}
|
||||
|
||||
k.ChanID = lnwire.NewShortChanIDFromInt(
|
||||
binary.BigEndian.Uint64(bs[:8]))
|
||||
k.HtlcID = binary.BigEndian.Uint64(bs[8:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bytes returns the serialized bytes for this circuit key.
|
||||
func (k CircuitKey) Bytes() []byte {
|
||||
bs := make([]byte, serializedCircuitKeyLen)
|
||||
binary.BigEndian.PutUint64(bs[:8], k.ChanID.ToUint64())
|
||||
binary.BigEndian.PutUint64(bs[8:], k.HtlcID)
|
||||
|
||||
return bs
|
||||
}
|
||||
|
||||
// Encode writes a CircuitKey to the provided io.Writer.
|
||||
func (k *CircuitKey) Encode(w io.Writer) error {
|
||||
var scratch [serializedCircuitKeyLen]byte
|
||||
binary.BigEndian.PutUint64(scratch[:8], k.ChanID.ToUint64())
|
||||
binary.BigEndian.PutUint64(scratch[8:], k.HtlcID)
|
||||
|
||||
_, err := w.Write(scratch[:])
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode reads a CircuitKey from the provided io.Reader.
|
||||
func (k *CircuitKey) Decode(r io.Reader) error {
|
||||
var scratch [serializedCircuitKeyLen]byte
|
||||
|
||||
if _, err := io.ReadFull(r, scratch[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
k.ChanID = lnwire.NewShortChanIDFromInt(
|
||||
binary.BigEndian.Uint64(scratch[:8]))
|
||||
k.HtlcID = binary.BigEndian.Uint64(scratch[8:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a string representation of the CircuitKey.
|
||||
func (k CircuitKey) String() string {
|
||||
return fmt.Sprintf("(Chan ID=%s, HTLC ID=%d)", k.ChanID, k.HtlcID)
|
||||
}
|
||||
Reference in new issue
Block a user