From ee0309f9421d22c53a6fe90a55b392e10d229701 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/000006_instantout.down.sql | 4 + .../sqlc/migrations/000006_instantout.up.sql | 52 +++ loopdb/sqlc/models.go | 21 + loopdb/sqlc/querier.go | 6 + loopdb/sqlc/queries/instantout.sql | 75 +++ 9 files changed, 958 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/000006_instantout.down.sql create mode 100644 loopdb/sqlc/migrations/000006_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..39b16d3 --- /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 83de95c..293d57d 100644 --- a/loopdb/sql_store.go +++ b/loopdb/sql_store.go @@ -543,7 +543,7 @@ func ConvertLoopOutRow(network *chaincfg.Params, row sqlc.GetLoopOutSwapRow, } if row.OutgoingChanSet != "" { - chanSet, err := convertOutgoingChanSet(row.OutgoingChanSet) + chanSet, err := ConvertOutgoingChanSet(row.OutgoingChanSet) if err != nil { return nil, err } @@ -666,9 +666,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/000006_instantout.down.sql b/loopdb/sqlc/migrations/000006_instantout.down.sql new file mode 100644 index 0000000..8829e9a --- /dev/null +++ b/loopdb/sqlc/migrations/000006_instantout.down.sql @@ -0,0 +1,4 @@ +DROP INDEX IF EXISTS instantout_updates_swap_hash_idx; +DROP INDEX IF EXISTS instantout_swap_hash_idx; +DROP TABLE IF EXISTS instantout_updates; +DROP TABLE IF EXISTS instantout_swaps; \ No newline at end of file diff --git a/loopdb/sqlc/migrations/000006_instantout.up.sql b/loopdb/sqlc/migrations/000006_instantout.up.sql new file mode 100644 index 0000000..a7ccd29 --- /dev/null +++ b/loopdb/sqlc/migrations/000006_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, + + -- outgoing_chan_set 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 d10b67d..869bf62 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 a5578ba..bf9f71e 100644 --- a/loopdb/sqlc/querier.go +++ b/loopdb/sqlc/querier.go @@ -13,6 +13,9 @@ type Querier interface { CreateReservation(ctx context.Context, arg CreateReservationParams) error FetchLiquidityParams(ctx context.Context) ([]byte, error) GetBatchSweeps(ctx context.Context, batchID int32) ([]GetBatchSweepsRow, 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) @@ -25,12 +28,15 @@ type Querier interface { GetUnconfirmedBatches(ctx context.Context) ([]SweepBatch, error) InsertBatch(ctx context.Context, arg InsertBatchParams) (int32, 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 UpdateBatch(ctx context.Context, arg UpdateBatchParams) error + UpdateInstantOut(ctx context.Context, arg UpdateInstantOutParams) error UpdateReservation(ctx context.Context, arg UpdateReservationParams) error UpsertLiquidityParams(ctx context.Context, params []byte) error UpsertSweep(ctx context.Context, arg UpsertSweepParams) 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;