From 22de4e0556c309160f238edd600ad8cf8cb42318 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Tue, 21 Nov 2023 15:50:21 +0100 Subject: [PATCH] instantout: add instantout store --- instantout/store.go | 432 ++++++++++++++++++ instantout/store_test.go | 36 ++ loopdb/sql_store.go | 6 +- loopdb/sqlc/instantout.sql.go | 329 +++++++++++++ .../migrations/000004_instantout.down.sql | 5 + .../sqlc/migrations/000004_instantout.up.sql | 52 +++ loopdb/sqlc/models.go | 21 + loopdb/sqlc/querier.go | 6 + loopdb/sqlc/queries/instantout.sql | 75 +++ 9 files changed, 959 insertions(+), 3 deletions(-) create mode 100644 instantout/store.go create mode 100644 instantout/store_test.go create mode 100644 loopdb/sqlc/instantout.sql.go create mode 100644 loopdb/sqlc/migrations/000004_instantout.down.sql create mode 100644 loopdb/sqlc/migrations/000004_instantout.up.sql create mode 100644 loopdb/sqlc/queries/instantout.sql diff --git a/instantout/store.go b/instantout/store.go new file mode 100644 index 0000000..ea07a4f --- /dev/null +++ b/instantout/store.go @@ -0,0 +1,432 @@ +package instantout + +import ( + "bytes" + "context" + "database/sql" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/loop/instantout/reservation" + "github.com/lightninglabs/loop/loopdb" + "github.com/lightninglabs/loop/loopdb/sqlc" + "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" +) + +// InstantOutBaseDB is the interface that contains all the queries generated +// by sqlc for the instantout table. +type InstantOutBaseDB interface { + // InsertSwap inserts a new base swap. + InsertSwap(ctx context.Context, arg sqlc.InsertSwapParams) error + + // InsertHtlcKeys inserts the htlc keys for a swap. + InsertHtlcKeys(ctx context.Context, arg sqlc.InsertHtlcKeysParams) error + + // InsertInstantOut inserts a new instant out swap. + InsertInstantOut(ctx context.Context, + arg sqlc.InsertInstantOutParams) error + + // InsertInstantOutUpdate inserts a new instant out update. + InsertInstantOutUpdate(ctx context.Context, + arg sqlc.InsertInstantOutUpdateParams) error + + // UpdateInstantOut updates an instant out swap. + UpdateInstantOut(ctx context.Context, + arg sqlc.UpdateInstantOutParams) error + + // GetInstantOutSwap retrieves an instant out swap. + GetInstantOutSwap(ctx context.Context, + swapHash []byte) (sqlc.GetInstantOutSwapRow, error) + + // GetInstantOutSwapUpdates retrieves all instant out swap updates. + GetInstantOutSwapUpdates(ctx context.Context, + swapHash []byte) ([]sqlc.InstantoutUpdate, error) + + // GetInstantOutSwaps retrieves all instant out swaps. + GetInstantOutSwaps(ctx context.Context) ([]sqlc.GetInstantOutSwapsRow, + error) + + // ExecTx allows for executing a function in the context of a database + // transaction. + ExecTx(ctx context.Context, txOptions loopdb.TxOptions, + txBody func(*sqlc.Queries) error) error +} + +// ReservationStore is the interface that is required to load the reservations +// based on the stored reservation ids. +type ReservationStore interface { + // GetReservation returns the reservation for the given id. + GetReservation(ctx context.Context, id reservation.ID) ( + *reservation.Reservation, error) +} + +type SQLStore struct { + baseDb InstantOutBaseDB + reservationStore ReservationStore + clock clock.Clock + network *chaincfg.Params +} + +// NewSQLStore creates a new SQLStore. +func NewSQLStore(db InstantOutBaseDB, clock clock.Clock, + reservationStore ReservationStore, network *chaincfg.Params) *SQLStore { + + return &SQLStore{ + baseDb: db, + clock: clock, + reservationStore: reservationStore, + } +} + +// CreateInstantLoopOut adds a new instant loop out to the store. +func (s *SQLStore) CreateInstantLoopOut(ctx context.Context, + instantOut *InstantOut) error { + + swapArgs := sqlc.InsertSwapParams{ + SwapHash: instantOut.SwapHash[:], + Preimage: instantOut.SwapPreimage[:], + InitiationTime: s.clock.Now(), + AmountRequested: int64(instantOut.Value), + CltvExpiry: instantOut.CltvExpiry, + MaxMinerFee: 0, + MaxSwapFee: 0, + InitiationHeight: instantOut.InitiationHeight, + ProtocolVersion: int32(instantOut.ProtocolVersion), + Label: "", + } + + htlcKeyArgs := sqlc.InsertHtlcKeysParams{ + SwapHash: instantOut.SwapHash[:], + SenderScriptPubkey: instantOut.ServerPubkey. + SerializeCompressed(), + ReceiverScriptPubkey: instantOut.ClientPubkey. + SerializeCompressed(), + ClientKeyFamily: int32(instantOut.KeyLocator.Family), + ClientKeyIndex: int32(instantOut.KeyLocator.Index), + } + + reservationIdByteSlice := reservationIdsToByteSlice( + instantOut.Reservations, + ) + instantOutArgs := sqlc.InsertInstantOutParams{ + SwapHash: instantOut.SwapHash[:], + Preimage: instantOut.SwapPreimage[:], + SweepAddress: instantOut.SweepAddress.String(), + OutgoingChanSet: instantOut.OutgoingChanSet.String(), + HtlcFeeRate: int64(instantOut.HtlcFeeRate), + ReservationIds: reservationIdByteSlice, + SwapInvoice: instantOut.SwapInvoice, + } + + updateArgs := sqlc.InsertInstantOutUpdateParams{ + SwapHash: instantOut.SwapHash[:], + UpdateTimestamp: s.clock.Now(), + UpdateState: string(instantOut.State), + } + + return s.baseDb.ExecTx(ctx, &loopdb.SqliteTxOptions{}, + func(q *sqlc.Queries) error { + err := q.InsertSwap(ctx, swapArgs) + if err != nil { + return err + } + + err = q.InsertHtlcKeys(ctx, htlcKeyArgs) + if err != nil { + return err + } + + err = q.InsertInstantOut(ctx, instantOutArgs) + if err != nil { + return err + } + + return q.InsertInstantOutUpdate(ctx, updateArgs) + }) +} + +// UpdateInstantLoopOut updates an existing instant loop out in the +// store. +func (s *SQLStore) UpdateInstantLoopOut(ctx context.Context, + instantOut *InstantOut) error { + + // Serialize the FinalHtlcTx. + var finalHtlcTx []byte + if instantOut.FinalizedHtlcTx != nil { + var buffer bytes.Buffer + err := instantOut.FinalizedHtlcTx.Serialize( + &buffer, + ) + if err != nil { + return err + } + finalHtlcTx = buffer.Bytes() + } + + var finalSweeplessSweepTx []byte + if instantOut.FinalizedSweeplessSweepTx != nil { + var buffer bytes.Buffer + err := instantOut.FinalizedSweeplessSweepTx.Serialize( + &buffer, + ) + if err != nil { + return err + } + finalSweeplessSweepTx = buffer.Bytes() + } + + var sweepTxHash []byte + if instantOut.SweepTxHash != nil { + sweepTxHash = instantOut.SweepTxHash[:] + } + + updateParams := sqlc.UpdateInstantOutParams{ + SwapHash: instantOut.SwapHash[:], + FinalizedHtlcTx: finalHtlcTx, + SweepTxHash: sweepTxHash, + FinalizedSweeplessSweepTx: finalSweeplessSweepTx, + SweepConfirmationHeight: serializeNullInt32( + int32(instantOut.SweepConfirmationHeight), + ), + } + + updateArgs := sqlc.InsertInstantOutUpdateParams{ + SwapHash: instantOut.SwapHash[:], + UpdateTimestamp: s.clock.Now(), + UpdateState: string(instantOut.State), + } + + return s.baseDb.ExecTx(ctx, &loopdb.SqliteTxOptions{}, + func(q *sqlc.Queries) error { + err := q.UpdateInstantOut(ctx, updateParams) + if err != nil { + return err + } + + return q.InsertInstantOutUpdate(ctx, updateArgs) + }, + ) +} + +// GetInstantLoopOut returns the instant loop out for the given swap +// hash. +func (s *SQLStore) GetInstantLoopOut(ctx context.Context, swapHash []byte) ( + *InstantOut, error) { + + row, err := s.baseDb.GetInstantOutSwap(ctx, swapHash) + if err != nil { + return nil, err + } + + updates, err := s.baseDb.GetInstantOutSwapUpdates(ctx, swapHash) + if err != nil { + return nil, err + } + + return s.sqlInstantOutToInstantOut(ctx, row, updates) +} + +// ListInstantLoopOuts returns all instant loop outs that are in the +// store. +func (s *SQLStore) ListInstantLoopOuts(ctx context.Context) ([]*InstantOut, + error) { + + rows, err := s.baseDb.GetInstantOutSwaps(ctx) + if err != nil { + return nil, err + } + + var instantOuts []*InstantOut + for _, row := range rows { + updates, err := s.baseDb.GetInstantOutSwapUpdates( + ctx, row.SwapHash, + ) + if err != nil { + return nil, err + } + + instantOut, err := s.sqlInstantOutToInstantOut( + ctx, sqlc.GetInstantOutSwapRow(row), updates, + ) + if err != nil { + return nil, err + } + + instantOuts = append(instantOuts, instantOut) + } + + return instantOuts, nil +} + +// sqlInstantOutToInstantOut converts sql rows to an instant out struct. +func (s *SQLStore) sqlInstantOutToInstantOut(ctx context.Context, + row sqlc.GetInstantOutSwapRow, updates []sqlc.InstantoutUpdate) ( + *InstantOut, error) { + + swapHash, err := lntypes.MakeHash(row.SwapHash) + if err != nil { + return nil, err + } + + swapPreImage, err := lntypes.MakePreimage(row.Preimage) + if err != nil { + return nil, err + } + + serverKey, err := btcec.ParsePubKey(row.SenderScriptPubkey) + if err != nil { + return nil, err + } + + clientKey, err := btcec.ParsePubKey(row.ReceiverScriptPubkey) + if err != nil { + return nil, err + } + + var finalizedHtlcTx *wire.MsgTx + if row.FinalizedHtlcTx != nil { + finalizedHtlcTx = &wire.MsgTx{} + err := finalizedHtlcTx.Deserialize(bytes.NewReader( + row.FinalizedHtlcTx, + )) + if err != nil { + return nil, err + } + } + + var finalizedSweepLessSweepTx *wire.MsgTx + if row.FinalizedSweeplessSweepTx != nil { + finalizedSweepLessSweepTx = &wire.MsgTx{} + err := finalizedSweepLessSweepTx.Deserialize(bytes.NewReader( + row.FinalizedSweeplessSweepTx, + )) + if err != nil { + return nil, err + } + } + + var sweepTxHash *chainhash.Hash + if row.SweepTxHash != nil { + sweepTxHash, err = chainhash.NewHash(row.SweepTxHash) + if err != nil { + return nil, err + } + } + + var outgoingChanSet loopdb.ChannelSet + if row.OutgoingChanSet != "" { + outgoingChanSet, err = loopdb.ConvertOutgoingChanSet( + row.OutgoingChanSet, + ) + if err != nil { + return nil, err + } + } + reservationIds, err := byteSliceToReservationIds(row.ReservationIds) + if err != nil { + return nil, err + } + + reservations := make([]*reservation.Reservation, 0, len(reservationIds)) + for _, id := range reservationIds { + reservation, err := s.reservationStore.GetReservation( + ctx, id, + ) + if err != nil { + return nil, err + } + + reservations = append(reservations, reservation) + } + + sweepAddress, err := btcutil.DecodeAddress(row.SweepAddress, s.network) + if err != nil { + return nil, err + } + + instantOut := &InstantOut{ + SwapHash: swapHash, + SwapPreimage: swapPreImage, + CltvExpiry: row.CltvExpiry, + OutgoingChanSet: outgoingChanSet, + Reservations: reservations, + ProtocolVersion: ProtocolVersion(row.ProtocolVersion), + InitiationHeight: row.InitiationHeight, + Value: btcutil.Amount(row.AmountRequested), + KeyLocator: keychain.KeyLocator{ + Family: keychain.KeyFamily(row.ClientKeyFamily), + Index: uint32(row.ClientKeyIndex), + }, + ClientPubkey: clientKey, + ServerPubkey: serverKey, + SwapInvoice: row.SwapInvoice, + HtlcFeeRate: chainfee.SatPerKWeight(row.HtlcFeeRate), + SweepAddress: sweepAddress, + FinalizedHtlcTx: finalizedHtlcTx, + SweepTxHash: sweepTxHash, + FinalizedSweeplessSweepTx: finalizedSweepLessSweepTx, + SweepConfirmationHeight: uint32(deserializeNullInt32( + row.SweepConfirmationHeight, + )), + } + + if len(updates) > 0 { + lastUpdate := updates[len(updates)-1] + instantOut.State = fsm.StateType(lastUpdate.UpdateState) + } + + return instantOut, nil +} + +// reservationIdsToByteSlice converts a slice of reservation ids to a byte +// slice. +func reservationIdsToByteSlice(reservations []*reservation.Reservation) []byte { + var reservationIds []byte + for _, reservation := range reservations { + reservationIds = append(reservationIds, reservation.ID[:]...) + } + + return reservationIds +} + +// byteSliceToReservationIds converts a byte slice to a slice of reservation +// ids. +func byteSliceToReservationIds(byteSlice []byte) ([]reservation.ID, error) { + if len(byteSlice)%32 != 0 { + return nil, fmt.Errorf("invalid byte slice length") + } + + var reservationIds []reservation.ID + for i := 0; i < len(byteSlice); i += 32 { + var id reservation.ID + copy(id[:], byteSlice[i:i+32]) + reservationIds = append(reservationIds, id) + } + + return reservationIds, nil +} + +// serializeNullInt32 serializes an int32 to a sql.NullInt32. +func serializeNullInt32(i int32) sql.NullInt32 { + return sql.NullInt32{ + Int32: i, + Valid: true, + } +} + +// deserializeNullInt32 deserializes an int32 from a sql.NullInt32. +func deserializeNullInt32(i sql.NullInt32) int32 { + if i.Valid { + return i.Int32 + } + + return 0 +} diff --git a/instantout/store_test.go b/instantout/store_test.go new file mode 100644 index 0000000..c8e6c33 --- /dev/null +++ b/instantout/store_test.go @@ -0,0 +1,36 @@ +package instantout + +import ( + "crypto/rand" + "testing" + + "github.com/lightninglabs/loop/instantout/reservation" + "github.com/stretchr/testify/require" +) + +func TestConvertingReservations(t *testing.T) { + var resId1, resId2 reservation.ID + + // fill the ids with random values. + if _, err := rand.Read(resId1[:]); err != nil { + t.Fatal(err) + } + + if _, err := rand.Read(resId2[:]); err != nil { + t.Fatal(err) + } + + reservations := []*reservation.Reservation{ + {ID: resId1}, {ID: resId2}, + } + + byteSlice := reservationIdsToByteSlice(reservations) + require.Len(t, byteSlice, 64) + + reservationIds, err := byteSliceToReservationIds(byteSlice) + require.NoError(t, err) + + require.Len(t, reservationIds, 2) + require.Equal(t, resId1, reservationIds[0]) + require.Equal(t, resId2, reservationIds[1]) +} diff --git a/loopdb/sql_store.go b/loopdb/sql_store.go index 9613ec5..9460c32 100644 --- a/loopdb/sql_store.go +++ b/loopdb/sql_store.go @@ -537,7 +537,7 @@ func (s *BaseDB) convertLoopOutRow(row sqlc.GetLoopOutSwapRow, } if row.OutgoingChanSet != "" { - chanSet, err := convertOutgoingChanSet(row.OutgoingChanSet) + chanSet, err := ConvertOutgoingChanSet(row.OutgoingChanSet) if err != nil { return nil, err } @@ -660,9 +660,9 @@ func getSwapEvents(updates []sqlc.SwapUpdate) ([]*LoopEvent, error) { return events, nil } -// convertOutgoingChanSet converts a comma separated string of channel IDs into +// ConvertOutgoingChanSet converts a comma separated string of channel IDs into // a ChannelSet. -func convertOutgoingChanSet(outgoingChanSet string) (ChannelSet, error) { +func ConvertOutgoingChanSet(outgoingChanSet string) (ChannelSet, error) { // Split the string into a slice of strings chanStrings := strings.Split(outgoingChanSet, ",") channels := make([]uint64, len(chanStrings)) diff --git a/loopdb/sqlc/instantout.sql.go b/loopdb/sqlc/instantout.sql.go new file mode 100644 index 0000000..88ce11e --- /dev/null +++ b/loopdb/sqlc/instantout.sql.go @@ -0,0 +1,329 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.17.2 +// source: instantout.sql + +package sqlc + +import ( + "context" + "database/sql" + "time" +) + +const getInstantOutSwap = `-- name: GetInstantOutSwap :one +SELECT + swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label, + instantout_swaps.swap_hash, instantout_swaps.preimage, instantout_swaps.sweep_address, instantout_swaps.outgoing_chan_set, instantout_swaps.htlc_fee_rate, instantout_swaps.reservation_ids, instantout_swaps.swap_invoice, instantout_swaps.finalized_htlc_tx, instantout_swaps.sweep_tx_hash, instantout_swaps.finalized_sweepless_sweep_tx, instantout_swaps.sweep_confirmation_height, + htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index +FROM + swaps +JOIN + instantout_swaps ON swaps.swap_hash = instantout_swaps.swap_hash +JOIN + htlc_keys ON swaps.swap_hash = htlc_keys.swap_hash +WHERE + swaps.swap_hash = $1 +` + +type GetInstantOutSwapRow struct { + ID int32 + SwapHash []byte + Preimage []byte + InitiationTime time.Time + AmountRequested int64 + CltvExpiry int32 + MaxMinerFee int64 + MaxSwapFee int64 + InitiationHeight int32 + ProtocolVersion int32 + Label string + SwapHash_2 []byte + Preimage_2 []byte + SweepAddress string + OutgoingChanSet string + HtlcFeeRate int64 + ReservationIds []byte + SwapInvoice string + FinalizedHtlcTx []byte + SweepTxHash []byte + FinalizedSweeplessSweepTx []byte + SweepConfirmationHeight sql.NullInt32 + SwapHash_3 []byte + SenderScriptPubkey []byte + ReceiverScriptPubkey []byte + SenderInternalPubkey []byte + ReceiverInternalPubkey []byte + ClientKeyFamily int32 + ClientKeyIndex int32 +} + +func (q *Queries) GetInstantOutSwap(ctx context.Context, swapHash []byte) (GetInstantOutSwapRow, error) { + row := q.db.QueryRowContext(ctx, getInstantOutSwap, swapHash) + var i GetInstantOutSwapRow + err := row.Scan( + &i.ID, + &i.SwapHash, + &i.Preimage, + &i.InitiationTime, + &i.AmountRequested, + &i.CltvExpiry, + &i.MaxMinerFee, + &i.MaxSwapFee, + &i.InitiationHeight, + &i.ProtocolVersion, + &i.Label, + &i.SwapHash_2, + &i.Preimage_2, + &i.SweepAddress, + &i.OutgoingChanSet, + &i.HtlcFeeRate, + &i.ReservationIds, + &i.SwapInvoice, + &i.FinalizedHtlcTx, + &i.SweepTxHash, + &i.FinalizedSweeplessSweepTx, + &i.SweepConfirmationHeight, + &i.SwapHash_3, + &i.SenderScriptPubkey, + &i.ReceiverScriptPubkey, + &i.SenderInternalPubkey, + &i.ReceiverInternalPubkey, + &i.ClientKeyFamily, + &i.ClientKeyIndex, + ) + return i, err +} + +const getInstantOutSwapUpdates = `-- name: GetInstantOutSwapUpdates :many +SELECT + instantout_updates.id, instantout_updates.swap_hash, instantout_updates.update_state, instantout_updates.update_timestamp +FROM + instantout_updates +WHERE + instantout_updates.swap_hash = $1 +` + +func (q *Queries) GetInstantOutSwapUpdates(ctx context.Context, swapHash []byte) ([]InstantoutUpdate, error) { + rows, err := q.db.QueryContext(ctx, getInstantOutSwapUpdates, swapHash) + if err != nil { + return nil, err + } + defer rows.Close() + var items []InstantoutUpdate + for rows.Next() { + var i InstantoutUpdate + if err := rows.Scan( + &i.ID, + &i.SwapHash, + &i.UpdateState, + &i.UpdateTimestamp, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getInstantOutSwaps = `-- name: GetInstantOutSwaps :many +SELECT + swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label, + instantout_swaps.swap_hash, instantout_swaps.preimage, instantout_swaps.sweep_address, instantout_swaps.outgoing_chan_set, instantout_swaps.htlc_fee_rate, instantout_swaps.reservation_ids, instantout_swaps.swap_invoice, instantout_swaps.finalized_htlc_tx, instantout_swaps.sweep_tx_hash, instantout_swaps.finalized_sweepless_sweep_tx, instantout_swaps.sweep_confirmation_height, + htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index +FROM + swaps +JOIN + instantout_swaps ON swaps.swap_hash = instantout_swaps.swap_hash +JOIN + htlc_keys ON swaps.swap_hash = htlc_keys.swap_hash +ORDER BY + swaps.id +` + +type GetInstantOutSwapsRow struct { + ID int32 + SwapHash []byte + Preimage []byte + InitiationTime time.Time + AmountRequested int64 + CltvExpiry int32 + MaxMinerFee int64 + MaxSwapFee int64 + InitiationHeight int32 + ProtocolVersion int32 + Label string + SwapHash_2 []byte + Preimage_2 []byte + SweepAddress string + OutgoingChanSet string + HtlcFeeRate int64 + ReservationIds []byte + SwapInvoice string + FinalizedHtlcTx []byte + SweepTxHash []byte + FinalizedSweeplessSweepTx []byte + SweepConfirmationHeight sql.NullInt32 + SwapHash_3 []byte + SenderScriptPubkey []byte + ReceiverScriptPubkey []byte + SenderInternalPubkey []byte + ReceiverInternalPubkey []byte + ClientKeyFamily int32 + ClientKeyIndex int32 +} + +func (q *Queries) GetInstantOutSwaps(ctx context.Context) ([]GetInstantOutSwapsRow, error) { + rows, err := q.db.QueryContext(ctx, getInstantOutSwaps) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetInstantOutSwapsRow + for rows.Next() { + var i GetInstantOutSwapsRow + if err := rows.Scan( + &i.ID, + &i.SwapHash, + &i.Preimage, + &i.InitiationTime, + &i.AmountRequested, + &i.CltvExpiry, + &i.MaxMinerFee, + &i.MaxSwapFee, + &i.InitiationHeight, + &i.ProtocolVersion, + &i.Label, + &i.SwapHash_2, + &i.Preimage_2, + &i.SweepAddress, + &i.OutgoingChanSet, + &i.HtlcFeeRate, + &i.ReservationIds, + &i.SwapInvoice, + &i.FinalizedHtlcTx, + &i.SweepTxHash, + &i.FinalizedSweeplessSweepTx, + &i.SweepConfirmationHeight, + &i.SwapHash_3, + &i.SenderScriptPubkey, + &i.ReceiverScriptPubkey, + &i.SenderInternalPubkey, + &i.ReceiverInternalPubkey, + &i.ClientKeyFamily, + &i.ClientKeyIndex, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const insertInstantOut = `-- name: InsertInstantOut :exec +INSERT INTO instantout_swaps ( + swap_hash, + preimage, + sweep_address, + outgoing_chan_set, + htlc_fee_rate, + reservation_ids, + swap_invoice +) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7 +) +` + +type InsertInstantOutParams struct { + SwapHash []byte + Preimage []byte + SweepAddress string + OutgoingChanSet string + HtlcFeeRate int64 + ReservationIds []byte + SwapInvoice string +} + +func (q *Queries) InsertInstantOut(ctx context.Context, arg InsertInstantOutParams) error { + _, err := q.db.ExecContext(ctx, insertInstantOut, + arg.SwapHash, + arg.Preimage, + arg.SweepAddress, + arg.OutgoingChanSet, + arg.HtlcFeeRate, + arg.ReservationIds, + arg.SwapInvoice, + ) + return err +} + +const insertInstantOutUpdate = `-- name: InsertInstantOutUpdate :exec +INSERT INTO instantout_updates ( + swap_hash, + update_state, + update_timestamp +) VALUES ( + $1, + $2, + $3 +) +` + +type InsertInstantOutUpdateParams struct { + SwapHash []byte + UpdateState string + UpdateTimestamp time.Time +} + +func (q *Queries) InsertInstantOutUpdate(ctx context.Context, arg InsertInstantOutUpdateParams) error { + _, err := q.db.ExecContext(ctx, insertInstantOutUpdate, arg.SwapHash, arg.UpdateState, arg.UpdateTimestamp) + return err +} + +const updateInstantOut = `-- name: UpdateInstantOut :exec +UPDATE instantout_swaps +SET + finalized_htlc_tx = $2, + sweep_tx_hash = $3, + finalized_sweepless_sweep_tx = $4, + sweep_confirmation_height = $5 +WHERE + instantout_swaps.swap_hash = $1 +` + +type UpdateInstantOutParams struct { + SwapHash []byte + FinalizedHtlcTx []byte + SweepTxHash []byte + FinalizedSweeplessSweepTx []byte + SweepConfirmationHeight sql.NullInt32 +} + +func (q *Queries) UpdateInstantOut(ctx context.Context, arg UpdateInstantOutParams) error { + _, err := q.db.ExecContext(ctx, updateInstantOut, + arg.SwapHash, + arg.FinalizedHtlcTx, + arg.SweepTxHash, + arg.FinalizedSweeplessSweepTx, + arg.SweepConfirmationHeight, + ) + return err +} diff --git a/loopdb/sqlc/migrations/000004_instantout.down.sql b/loopdb/sqlc/migrations/000004_instantout.down.sql new file mode 100644 index 0000000..367ad4d --- /dev/null +++ b/loopdb/sqlc/migrations/000004_instantout.down.sql @@ -0,0 +1,5 @@ +DROP TABLE IF EXISTS instantout_updates; +DROP INDEX IF EXISTS instantout_swap_hash_idx; +DROP TABLE IF EXISTS instantout_swaps; +DROP INDEX IF EXISTS instantout_updates_swap_hash_idx; +` \ No newline at end of file diff --git a/loopdb/sqlc/migrations/000004_instantout.up.sql b/loopdb/sqlc/migrations/000004_instantout.up.sql new file mode 100644 index 0000000..ee23a87 --- /dev/null +++ b/loopdb/sqlc/migrations/000004_instantout.up.sql @@ -0,0 +1,52 @@ +CREATE TABLE IF NOT EXISTS instantout_swaps ( + -- swap_hash points to the parent swap hash. + swap_hash BLOB PRIMARY KEY, + + -- preimage is the preimage of the swap. + preimage BLOB NOT NULL, + + -- sweep_address is the address that the server should sweep the funds to. + sweep_address TEXT NOT NULL, + + -- OutgoingChanSet is the set of short ids of channels that may be used. + -- If empty, any channel may be used. + outgoing_chan_set TEXT NOT NULL, + + -- htlc_fee_rate is the fee rate in sat/kw that is used for the htlc transaction. + htlc_fee_rate BIGINT NOT NULL, + + -- reservation_ids is a list of ids of the reservations that are used for this swap. + reservation_ids BLOB NOT NULL, + + -- swap_invoice is the invoice that is to be paid by the client to + -- initiate the loop out swap. + swap_invoice TEXT NOT NULL, + + -- finalized_htlc_tx contains the fully signed htlc transaction. + finalized_htlc_tx BLOB, + + -- sweep_tx_hash is the hash of the transaction that sweeps the htlc. + sweep_tx_hash BLOB, + + -- finalized_sweepless_sweep_tx contains the fully signed sweepless sweep transaction. + finalized_sweepless_sweep_tx BLOB, + + -- sweep_confirmation_height is the block height at which the sweep transaction is confirmed. + sweep_confirmation_height INTEGER +); + +CREATE TABLE IF NOT EXISTS instantout_updates ( + -- id is auto incremented for each update. + id INTEGER PRIMARY KEY, + + -- swap_hash is the hash of the swap that this update is for. + swap_hash BLOB NOT NULL REFERENCES instantout_swaps(swap_hash), + + -- update_state is the state of the swap at the time of the update. + update_state TEXT NOT NULL, + + -- update_timestamp is the time at which the update was created. + update_timestamp TIMESTAMP NOT NULL +); + +CREATE INDEX IF NOT EXISTS instantout_updates_swap_hash_idx ON instantout_updates(swap_hash); diff --git a/loopdb/sqlc/models.go b/loopdb/sqlc/models.go index 413b146..6529526 100644 --- a/loopdb/sqlc/models.go +++ b/loopdb/sqlc/models.go @@ -19,6 +19,27 @@ type HtlcKey struct { ClientKeyIndex int32 } +type InstantoutSwap struct { + SwapHash []byte + Preimage []byte + SweepAddress string + OutgoingChanSet string + HtlcFeeRate int64 + ReservationIds []byte + SwapInvoice string + FinalizedHtlcTx []byte + SweepTxHash []byte + FinalizedSweeplessSweepTx []byte + SweepConfirmationHeight sql.NullInt32 +} + +type InstantoutUpdate struct { + ID int32 + SwapHash []byte + UpdateState string + UpdateTimestamp time.Time +} + type LiquidityParam struct { ID int32 Params []byte diff --git a/loopdb/sqlc/querier.go b/loopdb/sqlc/querier.go index 69ae559..d6cc4f8 100644 --- a/loopdb/sqlc/querier.go +++ b/loopdb/sqlc/querier.go @@ -11,6 +11,9 @@ import ( type Querier interface { CreateReservation(ctx context.Context, arg CreateReservationParams) error FetchLiquidityParams(ctx context.Context) ([]byte, error) + GetInstantOutSwap(ctx context.Context, swapHash []byte) (GetInstantOutSwapRow, error) + GetInstantOutSwapUpdates(ctx context.Context, swapHash []byte) ([]InstantoutUpdate, error) + GetInstantOutSwaps(ctx context.Context) ([]GetInstantOutSwapsRow, error) GetLoopInSwap(ctx context.Context, swapHash []byte) (GetLoopInSwapRow, error) GetLoopInSwaps(ctx context.Context) ([]GetLoopInSwapsRow, error) GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopOutSwapRow, error) @@ -20,11 +23,14 @@ type Querier interface { GetReservations(ctx context.Context) ([]Reservation, error) GetSwapUpdates(ctx context.Context, swapHash []byte) ([]SwapUpdate, error) InsertHtlcKeys(ctx context.Context, arg InsertHtlcKeysParams) error + InsertInstantOut(ctx context.Context, arg InsertInstantOutParams) error + InsertInstantOutUpdate(ctx context.Context, arg InsertInstantOutUpdateParams) error InsertLoopIn(ctx context.Context, arg InsertLoopInParams) error InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) error InsertReservationUpdate(ctx context.Context, arg InsertReservationUpdateParams) error InsertSwap(ctx context.Context, arg InsertSwapParams) error InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error + UpdateInstantOut(ctx context.Context, arg UpdateInstantOutParams) error UpdateReservation(ctx context.Context, arg UpdateReservationParams) error UpsertLiquidityParams(ctx context.Context, params []byte) error } diff --git a/loopdb/sqlc/queries/instantout.sql b/loopdb/sqlc/queries/instantout.sql new file mode 100644 index 0000000..56f9052 --- /dev/null +++ b/loopdb/sqlc/queries/instantout.sql @@ -0,0 +1,75 @@ +-- name: InsertInstantOut :exec +INSERT INTO instantout_swaps ( + swap_hash, + preimage, + sweep_address, + outgoing_chan_set, + htlc_fee_rate, + reservation_ids, + swap_invoice +) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7 +); + +-- name: UpdateInstantOut :exec +UPDATE instantout_swaps +SET + finalized_htlc_tx = $2, + sweep_tx_hash = $3, + finalized_sweepless_sweep_tx = $4, + sweep_confirmation_height = $5 +WHERE + instantout_swaps.swap_hash = $1; + +-- name: InsertInstantOutUpdate :exec +INSERT INTO instantout_updates ( + swap_hash, + update_state, + update_timestamp +) VALUES ( + $1, + $2, + $3 +); + +-- name: GetInstantOutSwap :one +SELECT + swaps.*, + instantout_swaps.*, + htlc_keys.* +FROM + swaps +JOIN + instantout_swaps ON swaps.swap_hash = instantout_swaps.swap_hash +JOIN + htlc_keys ON swaps.swap_hash = htlc_keys.swap_hash +WHERE + swaps.swap_hash = $1; + +-- name: GetInstantOutSwaps :many +SELECT + swaps.*, + instantout_swaps.*, + htlc_keys.* +FROM + swaps +JOIN + instantout_swaps ON swaps.swap_hash = instantout_swaps.swap_hash +JOIN + htlc_keys ON swaps.swap_hash = htlc_keys.swap_hash +ORDER BY + swaps.id; + +-- name: GetInstantOutSwapUpdates :many +SELECT + instantout_updates.* +FROM + instantout_updates +WHERE + instantout_updates.swap_hash = $1;