From 4f5c806ba5e8b915cbb7485fb76099de42aa2a7d Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Thu, 30 May 2024 22:37:45 +0200 Subject: [PATCH] loopdb: add helper methods to update swap costs This commit adds the necessary sqlc code and SwapStore function to update swap costs for all swaps in one transaction. --- loopdb/interface.go | 5 ++ loopdb/sql_store.go | 32 +++++++++ loopdb/sql_test.go | 119 ++++++++++++++++++++++++++++++++++ loopdb/sqlc/querier.go | 2 + loopdb/sqlc/queries/swaps.sql | 16 +++++ loopdb/sqlc/swaps.sql.go | 41 ++++++++++++ loopdb/store.go | 8 +++ loopdb/store_mock.go | 22 +++++++ 8 files changed, 245 insertions(+) diff --git a/loopdb/interface.go b/loopdb/interface.go index 295c5364..ce8a1b97 100644 --- a/loopdb/interface.go +++ b/loopdb/interface.go @@ -65,6 +65,11 @@ type SwapStore interface { // it's decoding using the proto package's `Unmarshal` method. FetchLiquidityParams(ctx context.Context) ([]byte, error) + // BatchUpdateLoopOutSwapCosts updates the swap costs for a batch of + // loop out swaps. + BatchUpdateLoopOutSwapCosts(ctx context.Context, + swaps map[lntypes.Hash]SwapCost) error + // Close closes the underlying database. Close() error } diff --git a/loopdb/sql_store.go b/loopdb/sql_store.go index 9158e033..60e9d7a0 100644 --- a/loopdb/sql_store.go +++ b/loopdb/sql_store.go @@ -407,6 +407,38 @@ func (s *BaseDB) BatchInsertUpdate(ctx context.Context, }) } +// BatchUpdateLoopOutSwapCosts updates the swap costs for a batch of loop out +// swaps. +func (b *BaseDB) BatchUpdateLoopOutSwapCosts(ctx context.Context, + costs map[lntypes.Hash]SwapCost) error { + + writeOpts := &SqliteTxOptions{} + return b.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error { + for swapHash, cost := range costs { + lastUpdateID, err := tx.GetLastUpdateID( + ctx, swapHash[:], + ) + if err != nil { + return err + } + + err = tx.OverrideSwapCosts( + ctx, sqlc.OverrideSwapCostsParams{ + ID: lastUpdateID, + ServerCost: int64(cost.Server), + OnchainCost: int64(cost.Onchain), + OffchainCost: int64(cost.Offchain), + }, + ) + if err != nil { + return err + } + } + + return nil + }) +} + // loopToInsertArgs converts a SwapContract struct to the arguments needed to // insert it into the database. func loopToInsertArgs(hash lntypes.Hash, diff --git a/loopdb/sql_test.go b/loopdb/sql_test.go index b0082069..76ecf91f 100644 --- a/loopdb/sql_test.go +++ b/loopdb/sql_test.go @@ -13,6 +13,7 @@ import ( "github.com/lightninglabs/loop/loopdb/sqlc" "github.com/lightninglabs/loop/test" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" ) @@ -396,6 +397,124 @@ func TestIssue615(t *testing.T) { require.NoError(t, err) } +// TestBatchUpdateCost tests that we can batch update the cost of multiple swaps +// at once. +func TestBatchUpdateCost(t *testing.T) { + // Create a new sqlite store for testing. + store := NewTestDB(t) + + destAddr := test.GetDestAddr(t, 0) + initiationTime := time.Date(2018, 11, 1, 0, 0, 0, 0, time.UTC) + + testContract := LoopOutContract{ + SwapContract: SwapContract{ + AmountRequested: 100, + CltvExpiry: 144, + HtlcKeys: HtlcKeys{ + SenderScriptKey: senderKey, + ReceiverScriptKey: receiverKey, + SenderInternalPubKey: senderInternalKey, + ReceiverInternalPubKey: receiverInternalKey, + ClientScriptKeyLocator: keychain.KeyLocator{ + Family: 1, + Index: 2, + }, + }, + MaxMinerFee: 10, + MaxSwapFee: 20, + + InitiationHeight: 99, + + InitiationTime: initiationTime, + ProtocolVersion: ProtocolVersionMuSig2, + }, + MaxPrepayRoutingFee: 40, + PrepayInvoice: "prepayinvoice", + DestAddr: destAddr, + SwapInvoice: "swapinvoice", + MaxSwapRoutingFee: 30, + SweepConfTarget: 2, + HtlcConfirmations: 2, + SwapPublicationDeadline: initiationTime, + PaymentTimeout: time.Second * 11, + } + + makeSwap := func(preimage lntypes.Preimage) *LoopOutContract { + contract := testContract + contract.Preimage = preimage + + return &contract + } + + // Next, we'll add two swaps to the database. + preimage1 := testPreimage + preimage2 := lntypes.Preimage{4, 4, 4} + + ctxb := context.Background() + swap1 := makeSwap(preimage1) + swap2 := makeSwap(preimage2) + + hash1 := swap1.Preimage.Hash() + err := store.CreateLoopOut(ctxb, hash1, swap1) + require.NoError(t, err) + + hash2 := swap2.Preimage.Hash() + err = store.CreateLoopOut(ctxb, hash2, swap2) + require.NoError(t, err) + + // Add an update to both swaps containing the cost. + err = store.UpdateLoopOut( + ctxb, hash1, testTime, + SwapStateData{ + State: StateSuccess, + Cost: SwapCost{ + Server: 1, + Onchain: 2, + Offchain: 3, + }, + }, + ) + require.NoError(t, err) + + err = store.UpdateLoopOut( + ctxb, hash2, testTime, + SwapStateData{ + State: StateSuccess, + Cost: SwapCost{ + Server: 4, + Onchain: 5, + Offchain: 6, + }, + }, + ) + require.NoError(t, err) + + updateMap := map[lntypes.Hash]SwapCost{ + hash1: { + Server: 2, + Onchain: 3, + Offchain: 4, + }, + hash2: { + Server: 6, + Onchain: 7, + Offchain: 8, + }, + } + require.NoError(t, store.BatchUpdateLoopOutSwapCosts(ctxb, updateMap)) + + swaps, err := store.FetchLoopOutSwaps(ctxb) + require.NoError(t, err) + require.Len(t, swaps, 2) + + swapsMap := make(map[lntypes.Hash]*LoopOut) + swapsMap[swaps[0].Hash] = swaps[0] + swapsMap[swaps[1].Hash] = swaps[1] + + require.Equal(t, updateMap[hash1], swapsMap[hash1].State().Cost) + require.Equal(t, updateMap[hash2], swapsMap[hash2].State().Cost) +} + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" func randomString(length int) string { diff --git a/loopdb/sqlc/querier.go b/loopdb/sqlc/querier.go index d12c6ed7..c930b8b7 100644 --- a/loopdb/sqlc/querier.go +++ b/loopdb/sqlc/querier.go @@ -18,6 +18,7 @@ type Querier interface { GetInstantOutSwap(ctx context.Context, swapHash []byte) (GetInstantOutSwapRow, error) GetInstantOutSwapUpdates(ctx context.Context, swapHash []byte) ([]InstantoutUpdate, error) GetInstantOutSwaps(ctx context.Context) ([]GetInstantOutSwapsRow, error) + GetLastUpdateID(ctx context.Context, swapHash []byte) (int32, error) GetLoopInSwap(ctx context.Context, swapHash []byte) (GetLoopInSwapRow, error) GetLoopInSwaps(ctx context.Context) ([]GetLoopInSwapsRow, error) GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopOutSwapRow, error) @@ -38,6 +39,7 @@ type Querier interface { InsertReservationUpdate(ctx context.Context, arg InsertReservationUpdateParams) error InsertSwap(ctx context.Context, arg InsertSwapParams) error InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error + OverrideSwapCosts(ctx context.Context, arg OverrideSwapCostsParams) error UpdateBatch(ctx context.Context, arg UpdateBatchParams) error UpdateInstantOut(ctx context.Context, arg UpdateInstantOutParams) error UpdateReservation(ctx context.Context, arg UpdateReservationParams) error diff --git a/loopdb/sqlc/queries/swaps.sql b/loopdb/sqlc/queries/swaps.sql index 8d8dd66e..f44c4e34 100644 --- a/loopdb/sqlc/queries/swaps.sql +++ b/loopdb/sqlc/queries/swaps.sql @@ -133,3 +133,19 @@ INSERT INTO htlc_keys( ) VALUES ( $1, $2, $3, $4, $5, $6, $7 ); + +-- name: GetLastUpdateID :one +SELECT id +FROM swap_updates +WHERE swap_hash = $1 +ORDER BY update_timestamp DESC +LIMIT 1; + +-- name: OverrideSwapCosts :exec +UPDATE swap_updates +SET + server_cost = $2, + onchain_cost = $3, + offchain_cost = $4 +WHERE id = $1; + diff --git a/loopdb/sqlc/swaps.sql.go b/loopdb/sqlc/swaps.sql.go index cda5dec5..c5404d90 100644 --- a/loopdb/sqlc/swaps.sql.go +++ b/loopdb/sqlc/swaps.sql.go @@ -10,6 +10,21 @@ import ( "time" ) +const getLastUpdateID = `-- name: GetLastUpdateID :one +SELECT id +FROM swap_updates +WHERE swap_hash = $1 +ORDER BY update_timestamp DESC +LIMIT 1 +` + +func (q *Queries) GetLastUpdateID(ctx context.Context, swapHash []byte) (int32, error) { + row := q.db.QueryRowContext(ctx, getLastUpdateID, swapHash) + var id int32 + err := row.Scan(&id) + return id, err +} + const getLoopInSwap = `-- name: GetLoopInSwap :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, @@ -596,3 +611,29 @@ func (q *Queries) InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdatePara ) return err } + +const overrideSwapCosts = `-- name: OverrideSwapCosts :exec +UPDATE swap_updates +SET + server_cost = $2, + onchain_cost = $3, + offchain_cost = $4 +WHERE id = $1 +` + +type OverrideSwapCostsParams struct { + ID int32 + ServerCost int64 + OnchainCost int64 + OffchainCost int64 +} + +func (q *Queries) OverrideSwapCosts(ctx context.Context, arg OverrideSwapCostsParams) error { + _, err := q.db.ExecContext(ctx, overrideSwapCosts, + arg.ID, + arg.ServerCost, + arg.OnchainCost, + arg.OffchainCost, + ) + return err +} diff --git a/loopdb/store.go b/loopdb/store.go index 3726ea2d..dd6b6510 100644 --- a/loopdb/store.go +++ b/loopdb/store.go @@ -1009,3 +1009,11 @@ func (b *boltSwapStore) BatchInsertUpdate(ctx context.Context, return errUnimplemented } + +// BatchUpdateLoopOutSwapCosts updates the swap costs for a batch of loop out +// swaps. +func (b *boltSwapStore) BatchUpdateLoopOutSwapCosts(ctx context.Context, + costs map[lntypes.Hash]SwapCost) error { + + return errUnimplemented +} diff --git a/loopdb/store_mock.go b/loopdb/store_mock.go index 6057e2a4..c64b0c06 100644 --- a/loopdb/store_mock.go +++ b/loopdb/store_mock.go @@ -3,6 +3,7 @@ package loopdb import ( "context" "errors" + "fmt" "testing" "time" @@ -337,3 +338,24 @@ func (b *StoreMock) BatchInsertUpdate(ctx context.Context, return errors.New("not implemented") } + +// BatchUpdateLoopOutSwapCosts updates the swap costs for a batch of loop out +// swaps. +func (s *StoreMock) BatchUpdateLoopOutSwapCosts(ctx context.Context, + costs map[lntypes.Hash]SwapCost) error { + + for hash, cost := range costs { + if _, ok := s.LoopOutUpdates[hash]; !ok { + return fmt.Errorf("swap has no updates: %v", hash) + } + + updates, ok := s.LoopOutUpdates[hash] + if !ok { + return fmt.Errorf("swap has no updates: %v", hash) + } + + updates[len(updates)-1].Cost = cost + } + + return nil +}