2019-03-07 02:19:57 +00:00
|
|
|
package loopdb
|
|
|
|
|
|
|
|
import (
|
2020-05-19 07:54:27 +00:00
|
|
|
"bytes"
|
2019-03-07 02:19:57 +00:00
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2020-05-19 07:54:27 +00:00
|
|
|
"io"
|
2019-03-07 02:19:57 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"time"
|
|
|
|
|
2019-03-25 10:06:16 +00:00
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
2020-06-23 09:42:12 +00:00
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
2019-03-07 02:19:57 +00:00
|
|
|
"github.com/coreos/bbolt"
|
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// dbFileName is the default file name of the client-side loop sub-swap
|
|
|
|
// database.
|
|
|
|
dbFileName = "loop.db"
|
|
|
|
|
2019-03-12 15:09:57 +00:00
|
|
|
// loopOutBucketKey is a bucket that contains all out swaps that are
|
|
|
|
// currently pending or completed. This bucket is keyed by the swaphash,
|
|
|
|
// and leads to a nested sub-bucket that houses information for that
|
|
|
|
// swap.
|
2019-03-07 02:19:57 +00:00
|
|
|
//
|
|
|
|
// maps: swapHash -> swapBucket
|
2019-03-12 15:09:57 +00:00
|
|
|
loopOutBucketKey = []byte("uncharge-swaps")
|
2019-03-07 02:19:57 +00:00
|
|
|
|
2019-03-12 15:09:57 +00:00
|
|
|
// loopInBucketKey is a bucket that contains all in swaps that are
|
|
|
|
// currently pending or completed. This bucket is keyed by the swaphash,
|
|
|
|
// and leads to a nested sub-bucket that houses information for that
|
|
|
|
// swap.
|
2019-03-07 02:19:57 +00:00
|
|
|
//
|
2019-03-12 15:09:57 +00:00
|
|
|
// maps: swapHash -> swapBucket
|
|
|
|
loopInBucketKey = []byte("loop-in")
|
|
|
|
|
|
|
|
// updatesBucketKey is a bucket that contains all updates pertaining to
|
|
|
|
// a swap. This is a sub-bucket of the swap bucket for a particular
|
|
|
|
// swap. This list only ever grows.
|
|
|
|
//
|
|
|
|
// path: loopInBucket/loopOutBucket -> swapBucket[hash] -> updatesBucket
|
2019-03-07 02:19:57 +00:00
|
|
|
//
|
|
|
|
// maps: updateNumber -> time || state
|
|
|
|
updatesBucketKey = []byte("updates")
|
|
|
|
|
2020-06-23 09:00:02 +00:00
|
|
|
// basicStateKey contains the serialized basic swap state.
|
|
|
|
basicStateKey = []byte{0}
|
|
|
|
|
2020-06-23 09:42:12 +00:00
|
|
|
// htlcTxHashKey contains the confirmed htlc tx id.
|
|
|
|
htlcTxHashKey = []byte{1}
|
|
|
|
|
2019-03-07 02:19:57 +00:00
|
|
|
// contractKey is the key that stores the serialized swap contract. It
|
|
|
|
// is nested within the sub-bucket for each active swap.
|
|
|
|
//
|
2019-03-12 15:09:57 +00:00
|
|
|
// path: loopInBucket/loopOutBucket -> swapBucket[hash] -> contractKey
|
2019-03-07 02:19:57 +00:00
|
|
|
//
|
|
|
|
// value: time || rawSwapState
|
|
|
|
contractKey = []byte("contract")
|
|
|
|
|
2020-08-03 08:55:58 +00:00
|
|
|
// labelKey is the key that stores an optional label for the swap. If
|
|
|
|
// a swap was created before we started adding labels, or was created
|
|
|
|
// without a label, this key will not be present.
|
|
|
|
//
|
|
|
|
// path: loopInBucket/loopOutBucket -> swapBucket[hash] -> labelKey
|
|
|
|
//
|
|
|
|
// value: string label
|
|
|
|
labelKey = []byte("label")
|
|
|
|
|
2020-07-23 15:28:36 +00:00
|
|
|
// protocolVersionKey is used to optionally store the protocol version
|
|
|
|
// for the serialized swap contract. It is nested within the sub-bucket
|
|
|
|
// for each active swap.
|
|
|
|
//
|
|
|
|
// path: loopInBucket/loopOutBucket -> swapBucket[hash] -> protocolVersionKey
|
|
|
|
//
|
|
|
|
// value: protocol version as specified in server.proto
|
|
|
|
protocolVersionKey = []byte("protocol-version")
|
|
|
|
|
2020-05-19 07:54:27 +00:00
|
|
|
// outgoingChanSetKey is the key that stores a list of channel ids that
|
|
|
|
// restrict the loop out swap payment.
|
|
|
|
//
|
|
|
|
// path: loopOutBucket -> swapBucket[hash] -> outgoingChanSetKey
|
|
|
|
//
|
|
|
|
// value: concatenation of uint64 channel ids
|
|
|
|
outgoingChanSetKey = []byte("outgoing-chan-set")
|
|
|
|
|
2020-08-04 18:28:06 +00:00
|
|
|
// confirmationsKey is the key that stores the number of confirmations
|
|
|
|
// that were requested for a loop out swap.
|
|
|
|
//
|
|
|
|
// path: loopOutBucket -> swapBucket[hash] -> confirmationsKey
|
|
|
|
//
|
|
|
|
// value: uint32 confirmation value
|
|
|
|
confirmationsKey = []byte("confirmations")
|
|
|
|
|
2022-05-18 19:07:10 +00:00
|
|
|
// liquidtyBucket is a root bucket used to save liquidity manager
|
|
|
|
// related info.
|
|
|
|
liquidityBucket = []byte("liquidity")
|
|
|
|
|
|
|
|
// liquidtyParamsKey specifies the key used to store the liquidity
|
|
|
|
// parameters.
|
|
|
|
liquidtyParamsKey = []byte("params")
|
|
|
|
|
2022-04-24 20:32:17 +00:00
|
|
|
// keyLocatorKey is the key that stores the receiver key's locator info
|
|
|
|
// for loop outs or the sender key's locator info for loop ins. This is
|
|
|
|
// required for MuSig2 swaps. Only serialized/deserialized for swaps
|
|
|
|
// that have protocol version >= ProtocolVersionHtlcV3.
|
|
|
|
//
|
|
|
|
// path: loopInBucket/loopOutBucket -> swapBucket[hash] -> keyLocatorKey
|
|
|
|
//
|
|
|
|
// value: concatenation of uint32 values [family, index].
|
|
|
|
keyLocatorKey = []byte("keylocator")
|
|
|
|
|
2019-03-07 02:19:57 +00:00
|
|
|
byteOrder = binary.BigEndian
|
|
|
|
|
|
|
|
keyLength = 33
|
|
|
|
)
|
|
|
|
|
2021-04-26 13:35:20 +00:00
|
|
|
const (
|
|
|
|
// DefaultLoopOutHtlcConfirmations is the default number of
|
|
|
|
// confirmations we set for a loop out htlc.
|
|
|
|
DefaultLoopOutHtlcConfirmations uint32 = 1
|
|
|
|
|
|
|
|
// DefaultLoopDBTimeout is the default maximum time we wait for the
|
|
|
|
// Loop bbolt database to be opened. If the database is already opened
|
|
|
|
// by another process, the unique lock cannot be obtained. With the
|
|
|
|
// timeout we error out after the given time instead of just blocking
|
|
|
|
// for forever.
|
|
|
|
DefaultLoopDBTimeout = 5 * time.Second
|
|
|
|
)
|
2020-08-04 18:28:06 +00:00
|
|
|
|
2019-03-07 02:19:57 +00:00
|
|
|
// fileExists returns true if the file exists, and false otherwise.
|
|
|
|
func fileExists(path string) bool {
|
|
|
|
if _, err := os.Stat(path); err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// boltSwapStore stores swap data in boltdb.
|
|
|
|
type boltSwapStore struct {
|
2019-03-25 10:06:16 +00:00
|
|
|
db *bbolt.DB
|
|
|
|
chainParams *chaincfg.Params
|
2019-03-07 02:19:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// A compile-time flag to ensure that boltSwapStore implements the SwapStore
|
|
|
|
// interface.
|
|
|
|
var _ = (*boltSwapStore)(nil)
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
// NewBoltSwapStore creates a new client swap store.
|
2019-03-25 10:06:16 +00:00
|
|
|
func NewBoltSwapStore(dbPath string, chainParams *chaincfg.Params) (
|
|
|
|
*boltSwapStore, error) {
|
|
|
|
|
2019-03-07 02:19:57 +00:00
|
|
|
// If the target path for the swap store doesn't exist, then we'll
|
|
|
|
// create it now before we proceed.
|
|
|
|
if !fileExists(dbPath) {
|
|
|
|
if err := os.MkdirAll(dbPath, 0700); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that we know that path exists, we'll open up bolt, which
|
|
|
|
// implements our default swap store.
|
|
|
|
path := filepath.Join(dbPath, dbFileName)
|
2021-04-26 13:35:20 +00:00
|
|
|
bdb, err := bbolt.Open(path, 0600, &bbolt.Options{
|
|
|
|
Timeout: DefaultLoopDBTimeout,
|
|
|
|
})
|
|
|
|
if err == bbolt.ErrTimeout {
|
|
|
|
return nil, fmt.Errorf("%w: couldn't obtain exclusive lock on "+
|
|
|
|
"%s, timed out after %v", bbolt.ErrTimeout, path,
|
|
|
|
DefaultLoopDBTimeout)
|
|
|
|
}
|
2019-03-07 02:19:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll create all the buckets we need if this is the first time we're
|
|
|
|
// starting up. If they already exist, then these calls will be noops.
|
|
|
|
err = bdb.Update(func(tx *bbolt.Tx) error {
|
2019-05-15 11:59:05 +00:00
|
|
|
// Check if the meta bucket exists. If it exists, we consider
|
|
|
|
// the database as initialized and assume the meta bucket
|
|
|
|
// contains the db version.
|
|
|
|
metaBucket := tx.Bucket(metaBucketKey)
|
|
|
|
if metaBucket == nil {
|
|
|
|
log.Infof("Initializing new database with version %v",
|
|
|
|
latestDBVersion)
|
|
|
|
|
|
|
|
// Set db version to the current version.
|
|
|
|
err := setDBVersion(tx, latestDBVersion)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-03-07 02:19:57 +00:00
|
|
|
}
|
2019-05-15 11:59:05 +00:00
|
|
|
|
|
|
|
// Try creating these buckets, because loop in was added without
|
|
|
|
// bumping the db version number.
|
|
|
|
_, err = tx.CreateBucketIfNotExists(loopOutBucketKey)
|
2019-03-07 02:19:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-05-15 11:59:05 +00:00
|
|
|
|
|
|
|
_, err = tx.CreateBucketIfNotExists(loopInBucketKey)
|
2019-03-07 02:19:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-05-15 11:59:05 +00:00
|
|
|
|
2022-05-18 19:07:10 +00:00
|
|
|
// Create liquidity manager's bucket.
|
|
|
|
_, err = tx.CreateBucketIfNotExists(liquidityBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-03-07 02:19:57 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, before we start, we'll sync the DB versions to pick up any
|
|
|
|
// possible DB migrations.
|
2019-11-14 09:35:31 +00:00
|
|
|
err = syncVersions(bdb, chainParams)
|
2019-03-07 02:19:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &boltSwapStore{
|
2019-03-25 10:06:16 +00:00
|
|
|
db: bdb,
|
|
|
|
chainParams: chainParams,
|
2019-03-07 02:19:57 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
// FetchLoopOutSwaps returns all loop out swaps currently in the store.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the loopdb.SwapStore interface.
|
|
|
|
func (s *boltSwapStore) FetchLoopOutSwaps() ([]*LoopOut, error) {
|
|
|
|
var swaps []*LoopOut
|
2019-03-07 02:19:57 +00:00
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
err := s.db.View(func(tx *bbolt.Tx) error {
|
2019-03-12 15:09:57 +00:00
|
|
|
// First, we'll grab our main loop in bucket key.
|
2020-05-19 07:29:39 +00:00
|
|
|
rootBucket := tx.Bucket(loopOutBucketKey)
|
2019-03-07 02:19:57 +00:00
|
|
|
if rootBucket == nil {
|
|
|
|
return errors.New("bucket does not exist")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll now traverse the root bucket for all active swaps. The
|
|
|
|
// primary key is the swap hash itself.
|
|
|
|
return rootBucket.ForEach(func(swapHash, v []byte) error {
|
|
|
|
// Only go into things that we know are sub-bucket
|
|
|
|
// keys.
|
|
|
|
if v != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// From the root bucket, we'll grab the next swap
|
|
|
|
// bucket for this swap from its swaphash.
|
|
|
|
swapBucket := rootBucket.Bucket(swapHash)
|
|
|
|
if swapBucket == nil {
|
|
|
|
return fmt.Errorf("swap bucket %x not found",
|
|
|
|
swapHash)
|
|
|
|
}
|
|
|
|
|
|
|
|
// With the main swap bucket obtained, we'll grab the
|
|
|
|
// raw swap contract bytes and decode it.
|
|
|
|
contractBytes := swapBucket.Get(contractKey)
|
|
|
|
if contractBytes == nil {
|
|
|
|
return errors.New("contract not found")
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
contract, err := deserializeLoopOutContract(
|
|
|
|
contractBytes, s.chainParams,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-03 08:55:58 +00:00
|
|
|
// Get our label for this swap, if it is present.
|
|
|
|
contract.Label = getLabel(swapBucket)
|
|
|
|
|
2020-05-19 07:54:27 +00:00
|
|
|
// Read the list of concatenated outgoing channel ids
|
|
|
|
// that form the outgoing set.
|
|
|
|
setBytes := swapBucket.Get(outgoingChanSetKey)
|
|
|
|
if outgoingChanSetKey != nil {
|
|
|
|
r := bytes.NewReader(setBytes)
|
|
|
|
readLoop:
|
|
|
|
for {
|
|
|
|
var chanID uint64
|
|
|
|
err := binary.Read(r, byteOrder, &chanID)
|
|
|
|
switch {
|
|
|
|
case err == io.EOF:
|
|
|
|
break readLoop
|
|
|
|
case err != nil:
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
contract.OutgoingChanSet = append(
|
|
|
|
contract.OutgoingChanSet,
|
|
|
|
chanID,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-04 18:28:06 +00:00
|
|
|
// Set our default number of confirmations for the swap.
|
|
|
|
contract.HtlcConfirmations = DefaultLoopOutHtlcConfirmations
|
|
|
|
|
|
|
|
// If we have the number of confirmations stored for
|
|
|
|
// this swap, we overwrite our default with the stored
|
|
|
|
// value.
|
|
|
|
confBytes := swapBucket.Get(confirmationsKey)
|
|
|
|
if confBytes != nil {
|
|
|
|
r := bytes.NewReader(confBytes)
|
|
|
|
err := binary.Read(
|
|
|
|
r, byteOrder, &contract.HtlcConfirmations,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:23:32 +00:00
|
|
|
updates, err := deserializeUpdates(swapBucket)
|
2019-03-07 02:19:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-23 15:28:36 +00:00
|
|
|
// Try to unmarshal the protocol version for the swap.
|
|
|
|
// If the protocol version is not stored (which is
|
|
|
|
// the case for old clients), we'll assume the
|
|
|
|
// ProtocolVersionUnrecorded instead.
|
|
|
|
contract.ProtocolVersion, err =
|
|
|
|
UnmarshalProtocolVersion(
|
|
|
|
swapBucket.Get(protocolVersionKey),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-04-24 20:32:17 +00:00
|
|
|
// Try to unmarshal the key locator.
|
|
|
|
if contract.ProtocolVersion >= ProtocolVersionHtlcV3 {
|
|
|
|
contract.ClientKeyLocator, err = UnmarshalKeyLocator(
|
|
|
|
swapBucket.Get(keyLocatorKey),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
loop := LoopOut{
|
|
|
|
Loop: Loop{
|
|
|
|
Events: updates,
|
|
|
|
},
|
|
|
|
Contract: contract,
|
|
|
|
}
|
2019-03-07 02:19:57 +00:00
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
loop.Hash, err = lntypes.MakeHash(swapHash)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-03-07 02:19:57 +00:00
|
|
|
}
|
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
swaps = append(swaps, &loop)
|
|
|
|
|
|
|
|
return nil
|
2019-03-07 02:19:57 +00:00
|
|
|
})
|
|
|
|
})
|
2020-05-19 07:29:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return swaps, nil
|
2019-03-12 15:09:57 +00:00
|
|
|
}
|
|
|
|
|
2020-05-19 07:23:32 +00:00
|
|
|
// deserializeUpdates deserializes the list of swap updates that are stored as a
|
|
|
|
// key of the given bucket.
|
|
|
|
func deserializeUpdates(swapBucket *bbolt.Bucket) ([]*LoopEvent, error) {
|
|
|
|
// Once we have the raw swap, we'll also need to decode
|
|
|
|
// each of the past updates to the swap itself.
|
|
|
|
stateBucket := swapBucket.Bucket(updatesBucketKey)
|
|
|
|
if stateBucket == nil {
|
|
|
|
return nil, errors.New("updates bucket not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deserialize and collect each swap update into our slice of swap
|
|
|
|
// events.
|
|
|
|
var updates []*LoopEvent
|
2020-06-23 09:00:02 +00:00
|
|
|
err := stateBucket.ForEach(func(k, v []byte) error {
|
|
|
|
updateBucket := stateBucket.Bucket(k)
|
|
|
|
if updateBucket == nil {
|
|
|
|
return fmt.Errorf("expected state sub-bucket for %x", k)
|
|
|
|
}
|
|
|
|
|
|
|
|
basicState := updateBucket.Get(basicStateKey)
|
|
|
|
if basicState == nil {
|
|
|
|
return errors.New("no basic state for update")
|
|
|
|
}
|
|
|
|
|
|
|
|
event, err := deserializeLoopEvent(basicState)
|
2020-05-19 07:23:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-06-23 09:42:12 +00:00
|
|
|
// Deserialize htlc tx hash if this updates contains one.
|
|
|
|
htlcTxHashBytes := updateBucket.Get(htlcTxHashKey)
|
|
|
|
if htlcTxHashBytes != nil {
|
|
|
|
htlcTxHash, err := chainhash.NewHash(htlcTxHashBytes)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
event.HtlcTxHash = htlcTxHash
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:23:32 +00:00
|
|
|
updates = append(updates, event)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return updates, nil
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
// FetchLoopInSwaps returns all loop in swaps currently in the store.
|
2019-03-12 15:09:57 +00:00
|
|
|
//
|
|
|
|
// NOTE: Part of the loopdb.SwapStore interface.
|
2020-05-19 07:29:39 +00:00
|
|
|
func (s *boltSwapStore) FetchLoopInSwaps() ([]*LoopIn, error) {
|
|
|
|
var swaps []*LoopIn
|
2019-03-12 15:09:57 +00:00
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
err := s.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
// First, we'll grab our main loop in bucket key.
|
|
|
|
rootBucket := tx.Bucket(loopInBucketKey)
|
|
|
|
if rootBucket == nil {
|
|
|
|
return errors.New("bucket does not exist")
|
|
|
|
}
|
2019-03-12 15:09:57 +00:00
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
// We'll now traverse the root bucket for all active swaps. The
|
|
|
|
// primary key is the swap hash itself.
|
|
|
|
return rootBucket.ForEach(func(swapHash, v []byte) error {
|
|
|
|
// Only go into things that we know are sub-bucket
|
|
|
|
// keys.
|
|
|
|
if v != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2019-03-07 02:19:57 +00:00
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
// From the root bucket, we'll grab the next swap
|
|
|
|
// bucket for this swap from its swaphash.
|
|
|
|
swapBucket := rootBucket.Bucket(swapHash)
|
|
|
|
if swapBucket == nil {
|
|
|
|
return fmt.Errorf("swap bucket %x not found",
|
|
|
|
swapHash)
|
|
|
|
}
|
2019-03-07 02:19:57 +00:00
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
// With the main swap bucket obtained, we'll grab the
|
|
|
|
// raw swap contract bytes and decode it.
|
|
|
|
contractBytes := swapBucket.Get(contractKey)
|
|
|
|
if contractBytes == nil {
|
|
|
|
return errors.New("contract not found")
|
|
|
|
}
|
2019-03-07 02:19:57 +00:00
|
|
|
|
2019-03-12 15:09:57 +00:00
|
|
|
contract, err := deserializeLoopInContract(
|
|
|
|
contractBytes,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-03 08:55:58 +00:00
|
|
|
// Get our label for this swap, if it is present.
|
|
|
|
contract.Label = getLabel(swapBucket)
|
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
updates, err := deserializeUpdates(swapBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-23 15:28:36 +00:00
|
|
|
// Try to unmarshal the protocol version for the swap.
|
|
|
|
// If the protocol version is not stored (which is
|
|
|
|
// the case for old clients), we'll assume the
|
|
|
|
// ProtocolVersionUnrecorded instead.
|
|
|
|
contract.ProtocolVersion, err =
|
|
|
|
UnmarshalProtocolVersion(
|
|
|
|
swapBucket.Get(protocolVersionKey),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-04-24 20:32:17 +00:00
|
|
|
// Try to unmarshal the key locator.
|
|
|
|
if contract.ProtocolVersion >= ProtocolVersionHtlcV3 {
|
|
|
|
contract.ClientKeyLocator, err = UnmarshalKeyLocator(
|
|
|
|
swapBucket.Get(keyLocatorKey),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:29:39 +00:00
|
|
|
loop := LoopIn{
|
|
|
|
Loop: Loop{
|
|
|
|
Events: updates,
|
|
|
|
},
|
2019-03-12 15:09:57 +00:00
|
|
|
Contract: contract,
|
2020-05-19 07:29:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
loop.Hash, err = lntypes.MakeHash(swapHash)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
swaps = append(swaps, &loop)
|
2019-03-12 15:09:57 +00:00
|
|
|
|
|
|
|
return nil
|
2020-05-19 07:29:39 +00:00
|
|
|
})
|
|
|
|
})
|
2019-03-12 15:09:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-03-07 02:19:57 +00:00
|
|
|
}
|
|
|
|
|
2019-03-12 15:09:57 +00:00
|
|
|
return swaps, nil
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:01:09 +00:00
|
|
|
// createLoopBucket creates the bucket for a particular swap.
|
|
|
|
func createLoopBucket(tx *bbolt.Tx, swapTypeKey []byte, hash lntypes.Hash) (
|
|
|
|
*bbolt.Bucket, error) {
|
|
|
|
|
|
|
|
// First, we'll grab the root bucket that houses all of our
|
|
|
|
// swaps of this type.
|
|
|
|
swapTypeBucket, err := tx.CreateBucketIfNotExists(swapTypeKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the swap already exists, then we'll exit as we don't want
|
|
|
|
// to override a swap.
|
|
|
|
if swapTypeBucket.Get(hash[:]) != nil {
|
|
|
|
return nil, fmt.Errorf("swap %v already exists", hash)
|
|
|
|
}
|
|
|
|
|
|
|
|
// From the swap type bucket, we'll make a new sub swap bucket using the
|
|
|
|
// swap hash to store the individual swap.
|
|
|
|
return swapTypeBucket.CreateBucket(hash[:])
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateLoopOut adds an initiated swap to the store.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the loopdb.SwapStore interface.
|
|
|
|
func (s *boltSwapStore) CreateLoopOut(hash lntypes.Hash,
|
|
|
|
swap *LoopOutContract) error {
|
|
|
|
|
|
|
|
// If the hash doesn't match the pre-image, then this is an invalid
|
|
|
|
// swap so we'll bail out early.
|
|
|
|
if hash != swap.Preimage.Hash() {
|
|
|
|
return errors.New("hash and preimage do not match")
|
|
|
|
}
|
2019-03-12 15:09:57 +00:00
|
|
|
|
2019-03-07 02:19:57 +00:00
|
|
|
// Otherwise, we'll create a new swap within the database.
|
|
|
|
return s.db.Update(func(tx *bbolt.Tx) error {
|
2020-05-19 07:01:09 +00:00
|
|
|
// Create the swap bucket.
|
|
|
|
swapBucket, err := createLoopBucket(tx, loopOutBucketKey, hash)
|
2019-03-07 02:19:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:01:09 +00:00
|
|
|
// With the swap bucket created, we'll store the swap itself.
|
|
|
|
contractBytes, err := serializeLoopOutContract(swap)
|
2019-03-07 02:19:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-03-12 15:09:57 +00:00
|
|
|
err = swapBucket.Put(contractKey, contractBytes)
|
2019-03-07 02:19:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-03 08:55:58 +00:00
|
|
|
if err := putLabel(swapBucket, swap.Label); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:54:27 +00:00
|
|
|
// Write the outgoing channel set.
|
|
|
|
var b bytes.Buffer
|
|
|
|
for _, chanID := range swap.OutgoingChanSet {
|
|
|
|
err := binary.Write(&b, byteOrder, chanID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = swapBucket.Put(outgoingChanSetKey, b.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-03 08:55:58 +00:00
|
|
|
// Write label to disk if we have one.
|
|
|
|
if err := putLabel(swapBucket, swap.Label); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-04 18:28:06 +00:00
|
|
|
// Write our confirmation target under its own key.
|
|
|
|
var buf bytes.Buffer
|
|
|
|
err = binary.Write(&buf, byteOrder, swap.HtlcConfirmations)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = swapBucket.Put(confirmationsKey, buf.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-23 15:28:36 +00:00
|
|
|
// Store the current protocol version.
|
|
|
|
err = swapBucket.Put(protocolVersionKey,
|
|
|
|
MarshalProtocolVersion(swap.ProtocolVersion),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-04-24 20:32:17 +00:00
|
|
|
// Store the key locator for swaps with taproot htlc.
|
|
|
|
if swap.ProtocolVersion >= ProtocolVersionHtlcV3 {
|
|
|
|
keyLocator, err := MarshalKeyLocator(
|
|
|
|
swap.ClientKeyLocator,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = swapBucket.Put(keyLocatorKey, keyLocator)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-07 02:19:57 +00:00
|
|
|
// Finally, we'll create an empty updates bucket for this swap
|
|
|
|
// to track any future updates to the swap itself.
|
|
|
|
_, err = swapBucket.CreateBucket(updatesBucketKey)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-03-12 15:09:57 +00:00
|
|
|
// CreateLoopIn adds an initiated swap to the store.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the loopdb.SwapStore interface.
|
|
|
|
func (s *boltSwapStore) CreateLoopIn(hash lntypes.Hash,
|
|
|
|
swap *LoopInContract) error {
|
|
|
|
|
|
|
|
// If the hash doesn't match the pre-image, then this is an invalid
|
|
|
|
// swap so we'll bail out early.
|
|
|
|
if hash != swap.Preimage.Hash() {
|
|
|
|
return errors.New("hash and preimage do not match")
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:01:09 +00:00
|
|
|
// Otherwise, we'll create a new swap within the database.
|
|
|
|
return s.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
// Create the swap bucket.
|
|
|
|
swapBucket, err := createLoopBucket(tx, loopInBucketKey, hash)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// With the swap bucket created, we'll store the swap itself.
|
|
|
|
contractBytes, err := serializeLoopInContract(swap)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = swapBucket.Put(contractKey, contractBytes)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-03-12 15:09:57 +00:00
|
|
|
|
2020-07-23 15:28:36 +00:00
|
|
|
// Store the current protocol version.
|
|
|
|
err = swapBucket.Put(protocolVersionKey,
|
|
|
|
MarshalProtocolVersion(swap.ProtocolVersion),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-03 08:55:58 +00:00
|
|
|
// Write label to disk if we have one.
|
|
|
|
if err := putLabel(swapBucket, swap.Label); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-04-24 20:32:17 +00:00
|
|
|
// Store the key locator for swaps with taproot htlc.
|
|
|
|
if swap.ProtocolVersion >= ProtocolVersionHtlcV3 {
|
|
|
|
keyLocator, err := MarshalKeyLocator(
|
|
|
|
swap.ClientKeyLocator,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = swapBucket.Put(keyLocatorKey, keyLocator)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:01:09 +00:00
|
|
|
// Finally, we'll create an empty updates bucket for this swap
|
|
|
|
// to track any future updates to the swap itself.
|
|
|
|
_, err = swapBucket.CreateBucket(updatesBucketKey)
|
|
|
|
return err
|
|
|
|
})
|
2019-03-12 15:09:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// updateLoop saves a new swap state transition to the store. It takes in a
|
|
|
|
// bucket key so that this function can be used for both in and out swaps.
|
|
|
|
func (s *boltSwapStore) updateLoop(bucketKey []byte, hash lntypes.Hash,
|
2019-05-15 11:55:41 +00:00
|
|
|
time time.Time, state SwapStateData) error {
|
2019-03-07 02:19:57 +00:00
|
|
|
|
|
|
|
return s.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
// Starting from the root bucket, we'll traverse the bucket
|
|
|
|
// hierarchy all the way down to the swap bucket, and the
|
|
|
|
// update sub-bucket within that.
|
2019-03-12 15:09:57 +00:00
|
|
|
rootBucket := tx.Bucket(bucketKey)
|
2019-03-07 02:19:57 +00:00
|
|
|
if rootBucket == nil {
|
|
|
|
return errors.New("bucket does not exist")
|
|
|
|
}
|
|
|
|
swapBucket := rootBucket.Bucket(hash[:])
|
|
|
|
if swapBucket == nil {
|
|
|
|
return errors.New("swap not found")
|
|
|
|
}
|
2020-06-23 09:00:02 +00:00
|
|
|
updatesBucket := swapBucket.Bucket(updatesBucketKey)
|
|
|
|
if updatesBucket == nil {
|
2019-03-07 02:19:57 +00:00
|
|
|
return errors.New("udpate bucket not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Each update for this swap will get a new monotonically
|
|
|
|
// increasing ID number that we'll obtain now.
|
2020-06-23 09:00:02 +00:00
|
|
|
id, err := updatesBucket.NextSequence()
|
2019-03-07 02:19:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-06-23 09:00:02 +00:00
|
|
|
nextUpdateBucket, err := updatesBucket.CreateBucket(itob(id))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot create update bucket")
|
|
|
|
}
|
|
|
|
|
2019-03-07 02:19:57 +00:00
|
|
|
// With the ID obtained, we'll write out this new update value.
|
2019-03-12 15:09:57 +00:00
|
|
|
updateValue, err := serializeLoopEvent(time, state)
|
2019-03-07 02:19:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-06-23 09:00:02 +00:00
|
|
|
|
2020-06-23 09:42:12 +00:00
|
|
|
err = nextUpdateBucket.Put(basicStateKey, updateValue)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the htlc tx hash if available.
|
|
|
|
if state.HtlcTxHash != nil {
|
|
|
|
err := nextUpdateBucket.Put(
|
|
|
|
htlcTxHashKey, state.HtlcTxHash[:],
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2019-03-07 02:19:57 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-03-12 15:09:57 +00:00
|
|
|
// UpdateLoopOut stores a swap update. This appends to the event log for
|
|
|
|
// a particular swap as it goes through the various stages in its lifetime.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the loopdb.SwapStore interface.
|
|
|
|
func (s *boltSwapStore) UpdateLoopOut(hash lntypes.Hash, time time.Time,
|
2019-05-15 11:55:41 +00:00
|
|
|
state SwapStateData) error {
|
2019-03-12 15:09:57 +00:00
|
|
|
|
|
|
|
return s.updateLoop(loopOutBucketKey, hash, time, state)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateLoopIn stores a swap update. This appends to the event log for
|
|
|
|
// a particular swap as it goes through the various stages in its lifetime.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the loopdb.SwapStore interface.
|
|
|
|
func (s *boltSwapStore) UpdateLoopIn(hash lntypes.Hash, time time.Time,
|
2019-05-15 11:55:41 +00:00
|
|
|
state SwapStateData) error {
|
2019-03-12 15:09:57 +00:00
|
|
|
|
|
|
|
return s.updateLoop(loopInBucketKey, hash, time, state)
|
|
|
|
}
|
|
|
|
|
2019-03-07 02:19:57 +00:00
|
|
|
// Close closes the underlying database.
|
|
|
|
//
|
|
|
|
// NOTE: Part of the loopdb.SwapStore interface.
|
|
|
|
func (s *boltSwapStore) Close() error {
|
|
|
|
return s.db.Close()
|
|
|
|
}
|
2022-05-18 19:07:10 +00:00
|
|
|
|
|
|
|
// PutLiquidityParams writes the serialized `manager.Parameters` bytes into the
|
|
|
|
// bucket.
|
|
|
|
//
|
|
|
|
// NOTE: it's the caller's responsibility to encode the param. Atm, it's
|
|
|
|
// encoding using the proto package's `Marshal` method.
|
|
|
|
func (s *boltSwapStore) PutLiquidityParams(params []byte) error {
|
|
|
|
return s.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
// Read the root bucket.
|
|
|
|
rootBucket := tx.Bucket(liquidityBucket)
|
|
|
|
if rootBucket == nil {
|
|
|
|
return errors.New("liquidity bucket does not exist")
|
|
|
|
}
|
|
|
|
return rootBucket.Put(liquidtyParamsKey, params)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// FetchLiquidityParams reads the serialized `manager.Parameters` bytes from
|
|
|
|
// the bucket.
|
|
|
|
//
|
|
|
|
// NOTE: it's the caller's responsibility to decode the param. Atm, it's
|
|
|
|
// decoding using the proto package's `Unmarshal` method.
|
|
|
|
func (s *boltSwapStore) FetchLiquidityParams() ([]byte, error) {
|
|
|
|
var params []byte
|
|
|
|
|
|
|
|
err := s.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
// Read the root bucket.
|
|
|
|
rootBucket := tx.Bucket(liquidityBucket)
|
|
|
|
if rootBucket == nil {
|
|
|
|
return errors.New("liquidity bucket does not exist")
|
|
|
|
}
|
|
|
|
|
|
|
|
params = rootBucket.Get(liquidtyParamsKey)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return params, err
|
|
|
|
}
|