diff --git a/go.mod b/go.mod index 97a54488..c8a42e0a 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/jackc/pgconn v1.14.3 github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 + github.com/jackc/pgx/v5 v5.6.0 github.com/jessevdk/go-flags v1.4.0 github.com/lib/pq v1.10.9 github.com/lightninglabs/aperture v0.3.13-beta @@ -104,7 +105,6 @@ require ( github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/pgx/v4 v4.18.2 // indirect - github.com/jackc/pgx/v5 v5.6.0 // indirect github.com/jackc/puddle v1.3.0 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jackpal/gateway v1.0.5 // indirect diff --git a/loopd/daemon.go b/loopd/daemon.go index 7212933a..c5f1dc70 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -626,6 +626,16 @@ func (d *Daemon) initialize(withMacaroonService bool) error { clock.NewDefaultClock(), d.lnd.ChainParams, ) + // Run the deposit swap hash migration. + err = loopin.MigrateDepositSwapHash( + d.mainCtx, swapDb, depositStore, staticAddressLoopInStore, + ) + if err != nil { + errorf("Deposit swap hash migration failed: %v", err) + + return err + } + staticLoopInManager = loopin.NewManager(&loopin.Config{ Server: staticAddressClient, QuoteGetter: swapClient.Server, diff --git a/loopdb/sqlc/migrations/000017_deposit_swaphash.down.sql b/loopdb/sqlc/migrations/000017_deposit_swaphash.down.sql new file mode 100644 index 00000000..b2090f83 --- /dev/null +++ b/loopdb/sqlc/migrations/000017_deposit_swaphash.down.sql @@ -0,0 +1 @@ +ALTER TABLE deposits DROP COLUMN swap_hash; diff --git a/loopdb/sqlc/migrations/000017_deposit_swaphash.up.sql b/loopdb/sqlc/migrations/000017_deposit_swaphash.up.sql new file mode 100644 index 00000000..d400b047 --- /dev/null +++ b/loopdb/sqlc/migrations/000017_deposit_swaphash.up.sql @@ -0,0 +1 @@ +ALTER TABLE deposits ADD swap_hash BLOB; diff --git a/loopdb/sqlc/models.go b/loopdb/sqlc/models.go index 6728bb42..46766aca 100644 --- a/loopdb/sqlc/models.go +++ b/loopdb/sqlc/models.go @@ -19,6 +19,7 @@ type Deposit struct { TimeoutSweepPkScript []byte ExpirySweepTxid []byte FinalizedWithdrawalTx sql.NullString + SwapHash []byte } type DepositUpdate struct { diff --git a/loopdb/sqlc/querier.go b/loopdb/sqlc/querier.go index 26e4c055..d40bd762 100644 --- a/loopdb/sqlc/querier.go +++ b/loopdb/sqlc/querier.go @@ -19,6 +19,7 @@ type Querier interface { CreateWithdrawal(ctx context.Context, arg CreateWithdrawalParams) error CreateWithdrawalDeposit(ctx context.Context, arg CreateWithdrawalDepositParams) error DepositForOutpoint(ctx context.Context, arg DepositForOutpointParams) (Deposit, error) + DepositIDsForSwapHash(ctx context.Context, swapHash []byte) ([][]byte, error) FetchLiquidityParams(ctx context.Context) ([]byte, error) GetAllWithdrawals(ctx context.Context) ([]Withdrawal, error) GetBatchSweeps(ctx context.Context, batchID int32) ([]Sweep, error) @@ -62,7 +63,9 @@ type Querier interface { InsertSwap(ctx context.Context, arg InsertSwapParams) error InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error IsStored(ctx context.Context, swapHash []byte) (bool, error) + MapDepositToSwap(ctx context.Context, arg MapDepositToSwapParams) error OverrideSwapCosts(ctx context.Context, arg OverrideSwapCostsParams) error + SwapHashForDepositID(ctx context.Context, depositID []byte) ([]byte, error) UpdateBatch(ctx context.Context, arg UpdateBatchParams) error UpdateDeposit(ctx context.Context, arg UpdateDepositParams) error UpdateInstantOut(ctx context.Context, arg UpdateInstantOutParams) error diff --git a/loopdb/sqlc/queries/static_address_loopin.sql b/loopdb/sqlc/queries/static_address_loopin.sql index ea4344b7..9baadfbe 100644 --- a/loopdb/sqlc/queries/static_address_loopin.sql +++ b/loopdb/sqlc/queries/static_address_loopin.sql @@ -93,3 +93,30 @@ SELECT EXISTS ( FROM static_address_swaps WHERE swap_hash = $1 ); + +-- name: MapDepositToSwap :exec +UPDATE + deposits +SET + swap_hash = $2 +WHERE + deposit_id = $1; + +-- name: SwapHashForDepositID :one +SELECT + swap_hash +FROM + deposits +WHERE + deposit_id = $1; + +-- name: DepositIDsForSwapHash :many +SELECT + deposit_id +FROM + deposits +WHERE + swap_hash = $1; + + + diff --git a/loopdb/sqlc/static_address_deposits.sql.go b/loopdb/sqlc/static_address_deposits.sql.go index 3a5c8076..191f1f56 100644 --- a/loopdb/sqlc/static_address_deposits.sql.go +++ b/loopdb/sqlc/static_address_deposits.sql.go @@ -13,7 +13,7 @@ import ( const allDeposits = `-- name: AllDeposits :many SELECT - id, deposit_id, tx_hash, out_index, amount, confirmation_height, timeout_sweep_pk_script, expiry_sweep_txid, finalized_withdrawal_tx + id, deposit_id, tx_hash, out_index, amount, confirmation_height, timeout_sweep_pk_script, expiry_sweep_txid, finalized_withdrawal_tx, swap_hash FROM deposits ORDER BY @@ -39,6 +39,7 @@ func (q *Queries) AllDeposits(ctx context.Context) ([]Deposit, error) { &i.TimeoutSweepPkScript, &i.ExpirySweepTxid, &i.FinalizedWithdrawalTx, + &i.SwapHash, ); err != nil { return nil, err } @@ -102,7 +103,7 @@ func (q *Queries) CreateDeposit(ctx context.Context, arg CreateDepositParams) er const depositForOutpoint = `-- name: DepositForOutpoint :one SELECT - id, deposit_id, tx_hash, out_index, amount, confirmation_height, timeout_sweep_pk_script, expiry_sweep_txid, finalized_withdrawal_tx + id, deposit_id, tx_hash, out_index, amount, confirmation_height, timeout_sweep_pk_script, expiry_sweep_txid, finalized_withdrawal_tx, swap_hash FROM deposits WHERE @@ -129,13 +130,14 @@ func (q *Queries) DepositForOutpoint(ctx context.Context, arg DepositForOutpoint &i.TimeoutSweepPkScript, &i.ExpirySweepTxid, &i.FinalizedWithdrawalTx, + &i.SwapHash, ) return i, err } const getDeposit = `-- name: GetDeposit :one SELECT - id, deposit_id, tx_hash, out_index, amount, confirmation_height, timeout_sweep_pk_script, expiry_sweep_txid, finalized_withdrawal_tx + id, deposit_id, tx_hash, out_index, amount, confirmation_height, timeout_sweep_pk_script, expiry_sweep_txid, finalized_withdrawal_tx, swap_hash FROM deposits WHERE @@ -155,6 +157,7 @@ func (q *Queries) GetDeposit(ctx context.Context, depositID []byte) (Deposit, er &i.TimeoutSweepPkScript, &i.ExpirySweepTxid, &i.FinalizedWithdrawalTx, + &i.SwapHash, ) return i, err } diff --git a/loopdb/sqlc/static_address_loopin.sql.go b/loopdb/sqlc/static_address_loopin.sql.go index 22ee6438..6ce17730 100644 --- a/loopdb/sqlc/static_address_loopin.sql.go +++ b/loopdb/sqlc/static_address_loopin.sql.go @@ -11,6 +11,38 @@ import ( "time" ) +const depositIDsForSwapHash = `-- name: DepositIDsForSwapHash :many +SELECT + deposit_id +FROM + deposits +WHERE + swap_hash = $1 +` + +func (q *Queries) DepositIDsForSwapHash(ctx context.Context, swapHash []byte) ([][]byte, error) { + rows, err := q.db.QueryContext(ctx, depositIDsForSwapHash, swapHash) + if err != nil { + return nil, err + } + defer rows.Close() + var items [][]byte + for rows.Next() { + var deposit_id []byte + if err := rows.Scan(&deposit_id); err != nil { + return nil, err + } + items = append(items, deposit_id) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getLoopInSwapUpdates = `-- name: GetLoopInSwapUpdates :many SELECT static_address_swap_updates.id, static_address_swap_updates.swap_hash, static_address_swap_updates.update_state, static_address_swap_updates.update_timestamp @@ -328,6 +360,41 @@ func (q *Queries) IsStored(ctx context.Context, swapHash []byte) (bool, error) { return exists, err } +const mapDepositToSwap = `-- name: MapDepositToSwap :exec +UPDATE + deposits +SET + swap_hash = $2 +WHERE + deposit_id = $1 +` + +type MapDepositToSwapParams struct { + DepositID []byte + SwapHash []byte +} + +func (q *Queries) MapDepositToSwap(ctx context.Context, arg MapDepositToSwapParams) error { + _, err := q.db.ExecContext(ctx, mapDepositToSwap, arg.DepositID, arg.SwapHash) + return err +} + +const swapHashForDepositID = `-- name: SwapHashForDepositID :one +SELECT + swap_hash +FROM + deposits +WHERE + deposit_id = $1 +` + +func (q *Queries) SwapHashForDepositID(ctx context.Context, depositID []byte) ([]byte, error) { + row := q.db.QueryRowContext(ctx, swapHashForDepositID, depositID) + var swap_hash []byte + err := row.Scan(&swap_hash) + return swap_hash, err +} + const updateStaticAddressLoopIn = `-- name: UpdateStaticAddressLoopIn :exec UPDATE static_address_swaps SET diff --git a/staticaddr/loopin/deposit_swaphash_migration.go b/staticaddr/loopin/deposit_swaphash_migration.go new file mode 100644 index 00000000..512cb06c --- /dev/null +++ b/staticaddr/loopin/deposit_swaphash_migration.go @@ -0,0 +1,88 @@ +package loopin + +import ( + "context" + "fmt" + "time" + + "github.com/lightninglabs/loop/loopdb" + "github.com/lightninglabs/loop/staticaddr/deposit" + "github.com/lightningnetwork/lnd/lntypes" +) + +const ( + // depositSwapHashMigrationID is the identifier for the deposit swap + // hash migration. + depositSwapHashMigrationID = "deposit_swap_hash" +) + +// MigrateDepositSwapHash will retrieve the comma separated deposit list of +// past and pending swaps and map them to the swap hash in the deposits table. +func MigrateDepositSwapHash(ctx context.Context, db loopdb.SwapStore, + depositStore *deposit.SqlStore, swapStore *SqlStore) error { + + migrationDone, err := db.HasMigration( + ctx, depositSwapHashMigrationID, + ) + if err != nil { + return fmt.Errorf("unable to check migration status: %w", err) + } + if migrationDone { + log.Infof("Deposit swap hash migration already done, " + + "skipping") + + return nil + } + + log.Infof("Starting deposit swap hash migration") + startTs := time.Now() + defer func() { + log.Infof("Finished deposit swap hash migration in %v", + time.Since(startTs)) + }() + + // First we'll fetch all past loop in swaps from the database. + swaps, err := swapStore.GetStaticAddressLoopInSwapsByStates( + ctx, AllStates, + ) + if err != nil { + return err + } + + // Now we'll map each deposit of a swap to its respective swap hash. + depositsToSwapHashes := make(map[deposit.ID]lntypes.Hash) + for _, swap := range swaps { + for _, outpoint := range swap.DepositOutpoints { + deposit, err := depositStore.DepositForOutpoint( + ctx, outpoint, + ) + if err != nil { + return fmt.Errorf("unable to fetch deposit "+ + "for outpoint %s: %w", outpoint, err) + } + if deposit == nil { + return fmt.Errorf("deposit for outpoint %s "+ + "not found", outpoint) + } + + if _, ok := depositsToSwapHashes[deposit.ID]; !ok { + depositsToSwapHashes[deposit.ID] = swap.SwapHash + } else { + log.Warnf("Duplicate deposit ID %s found for "+ + "outpoint %s, skipping", + deposit.ID, outpoint) + } + } + } + + log.Infof("Batch-mapping %d deposits to swap hashes", + len(depositsToSwapHashes)) + + err = swapStore.BatchMapDepositsToSwapHashes(ctx, depositsToSwapHashes) + if err != nil { + return err + } + + // Finally mark the migration as done. + return db.SetMigration(ctx, depositSwapHashMigrationID) +} diff --git a/staticaddr/loopin/deposit_swaphash_migration_test.go b/staticaddr/loopin/deposit_swaphash_migration_test.go new file mode 100644 index 00000000..1db77fe9 --- /dev/null +++ b/staticaddr/loopin/deposit_swaphash_migration_test.go @@ -0,0 +1,195 @@ +package loopin + +import ( + "context" + "strings" + "testing" + "time" + + "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/loopdb" + "github.com/lightninglabs/loop/loopdb/sqlc" + "github.com/lightninglabs/loop/staticaddr/deposit" + "github.com/lightninglabs/loop/test" + "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/stretchr/testify/require" +) + +const ( + P2wkhAddr = "bcrt1qq68r6ff4k4pjx39efs44gcyccf7unqnu5qtjjz" +) + +// TestDepositSwapHashMigration tests deposit to swap hash migration. +func TestDepositSwapHashMigration(t *testing.T) { + // Set up test context objects. + ctxb := context.Background() + testDb := loopdb.NewTestDB(t) + testClock := clock.NewTestClock(time.Now()) + defer testDb.Close() + + db := loopdb.NewStoreMock(t) + depositStore := deposit.NewSqlStore(testDb.BaseDB) + swapStore := NewSqlStore( + loopdb.NewTypedStore[Querier](testDb), testClock, + &chaincfg.RegressionNetParams, + ) + + newID := func() deposit.ID { + did, err := deposit.GetRandomDepositID() + require.NoError(t, err) + + return did + } + + d1, d2 := &deposit.Deposit{ + ID: newID(), + OutPoint: wire.OutPoint{ + Hash: chainhash.Hash{0x1a, 0x2b, 0x3c, 0x4d}, + Index: 0, + }, + Value: btcutil.Amount(100_000), + TimeOutSweepPkScript: []byte{ + 0x00, 0x14, 0x1a, 0x2b, 0x3c, 0x41, + }, + }, + &deposit.Deposit{ + ID: newID(), + OutPoint: wire.OutPoint{ + Hash: chainhash.Hash{0x2a, 0x2b, 0x3c, 0x4e}, + Index: 1, + }, + Value: btcutil.Amount(200_000), + TimeOutSweepPkScript: []byte{ + 0x00, 0x14, 0x1a, 0x2b, 0x3c, 0x4d, + }, + } + + err := depositStore.CreateDeposit(ctxb, d1) + require.NoError(t, err) + err = depositStore.CreateDeposit(ctxb, d2) + require.NoError(t, err) + + outpoints := []string{ + d1.OutPoint.String(), + d2.OutPoint.String(), + } + _, clientPubKey := test.CreateKey(1) + _, serverPubKey := test.CreateKey(2) + addr, err := btcutil.DecodeAddress(P2wkhAddr, nil) + require.NoError(t, err) + + swapHash := lntypes.Hash{0x1, 0x2, 0x3, 0x4} + loopIn := StaticAddressLoopIn{ + SwapHash: swapHash, + DepositOutpoints: outpoints, + ClientPubkey: clientPubKey, + ServerPubkey: serverPubKey, + HtlcTimeoutSweepAddress: addr, + } + loopIn.SetState(Succeeded) + + // Insert the swap without the deposit mapping. + err = swapStore.baseDB.ExecTx(ctxb, loopdb.NewSqlWriteOpts(), + func(q Querier) error { + swapArgs := sqlc.InsertSwapParams{ + SwapHash: loopIn.SwapHash[:], + Preimage: loopIn.SwapPreimage[:], + InitiationTime: loopIn.InitiationTime, + AmountRequested: int64(loopIn.TotalDepositAmount()), + CltvExpiry: loopIn.HtlcCltvExpiry, + MaxSwapFee: int64(loopIn.MaxSwapFee), + InitiationHeight: int32(loopIn.InitiationHeight), + ProtocolVersion: int32(loopIn.ProtocolVersion), + Label: loopIn.Label, + } + + htlcKeyArgs := sqlc.InsertHtlcKeysParams{ + SwapHash: loopIn.SwapHash[:], + SenderScriptPubkey: loopIn.ClientPubkey.SerializeCompressed(), + ReceiverScriptPubkey: loopIn.ServerPubkey.SerializeCompressed(), + ClientKeyFamily: int32(loopIn.HtlcKeyLocator.Family), + ClientKeyIndex: int32(loopIn.HtlcKeyLocator.Index), + } + + // Sanity check, if any of the outpoints contain the outpoint separator. + // If so, we reject the loop-in to prevent potential issues with + // parsing. + for _, outpoint := range loopIn.DepositOutpoints { + if strings.Contains(outpoint, outpointSeparator) { + return ErrInvalidOutpoint + } + } + + joinedOutpoints := strings.Join( + loopIn.DepositOutpoints, outpointSeparator, + ) + staticAddressLoopInParams := sqlc.InsertStaticAddressLoopInParams{ + SwapHash: loopIn.SwapHash[:], + SwapInvoice: loopIn.SwapInvoice, + LastHop: loopIn.LastHop, + QuotedSwapFeeSatoshis: int64(loopIn.QuotedSwapFee), + HtlcTimeoutSweepAddress: loopIn.HtlcTimeoutSweepAddress.String(), + HtlcTxFeeRateSatKw: int64(loopIn.HtlcTxFeeRate), + DepositOutpoints: joinedOutpoints, + PaymentTimeoutSeconds: int32(loopIn.PaymentTimeoutSeconds), + } + + updateArgs := sqlc.InsertStaticAddressMetaUpdateParams{ + SwapHash: loopIn.SwapHash[:], + UpdateTimestamp: testClock.Now(), + UpdateState: string(loopIn.GetState()), + } + err := q.InsertSwap(ctxb, swapArgs) + if err != nil { + return err + } + + err = q.InsertHtlcKeys(ctxb, htlcKeyArgs) + if err != nil { + return err + } + + err = q.InsertStaticAddressLoopIn( + ctxb, staticAddressLoopInParams, + ) + if err != nil { + return err + } + + return q.InsertStaticAddressMetaUpdate(ctxb, updateArgs) + }, + ) + require.NoError(t, err) + + depositIDs, err := swapStore.DepositIDsForSwapHash(ctxb, swapHash) + require.NoError(t, err) + require.Len(t, depositIDs, 0) + + swapHashes, err := swapStore.SwapHashesForDepositIDs( + ctxb, []deposit.ID{d1.ID, d2.ID}, + ) + require.NoError(t, err) + require.Len(t, swapHashes, 0) + + err = MigrateDepositSwapHash(ctxb, db, depositStore, swapStore) + require.NoError(t, err) + + depositIDs, err = swapStore.DepositIDsForSwapHash(ctxb, swapHash) + require.NoError(t, err) + require.Len(t, depositIDs, 2) + require.Contains(t, depositIDs, d1.ID) + require.Contains(t, depositIDs, d2.ID) + + swapHashes, err = swapStore.SwapHashesForDepositIDs( + ctxb, []deposit.ID{d1.ID, d2.ID}, + ) + require.NoError(t, err) + require.Len(t, swapHashes, 1) + require.Len(t, swapHashes[swapHash], 2) + require.Contains(t, swapHashes[swapHash], d1.ID) + require.Contains(t, swapHashes[swapHash], d2.ID) +} diff --git a/staticaddr/loopin/sql_store.go b/staticaddr/loopin/sql_store.go index e6ba5aad..b50fc4d0 100644 --- a/staticaddr/loopin/sql_store.go +++ b/staticaddr/loopin/sql_store.go @@ -10,9 +10,11 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/jackc/pgx/v5" "github.com/lightninglabs/loop/fsm" "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/loopdb/sqlc" + "github.com/lightninglabs/loop/staticaddr/deposit" "github.com/lightninglabs/loop/staticaddr/version" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/keychain" @@ -68,6 +70,20 @@ type Querier interface { // IsStored returns true if a swap with the given hash is stored in the // database, false otherwise. IsStored(ctx context.Context, swapHash []byte) (bool, error) + + // MapDepositToSwap maps a deposit to a swap in the database. + MapDepositToSwap(ctx context.Context, + arg sqlc.MapDepositToSwapParams) error + + // SwapHashForDepositID retrieves the swap hash for the given deposit + // ID. + SwapHashForDepositID(ctx context.Context, + depositID []byte) ([]byte, error) + + // DepositIDsForSwapHash retrieves all deposit IDs for a given swap + // hash. + DepositIDsForSwapHash(ctx context.Context, + swapHash []byte) ([][]byte, error) } // BaseDB is the interface that contains all the queries generated by sqlc for @@ -306,6 +322,89 @@ func (s *SqlStore) IsStored(ctx context.Context, swapHash lntypes.Hash) (bool, return s.baseDB.IsStored(ctx, swapHash[:]) } +// BatchMapDepositsToSwapHashes maps multiple deposits to their respective swap +// hashes in a single transaction. +func (s *SqlStore) BatchMapDepositsToSwapHashes(ctx context.Context, + depositsToHashes map[deposit.ID]lntypes.Hash) error { + + return s.baseDB.ExecTx(ctx, loopdb.NewSqlWriteOpts(), + func(q Querier) error { + for deposit, swapHash := range depositsToHashes { + err := q.MapDepositToSwap( + ctx, sqlc.MapDepositToSwapParams{ + DepositID: deposit[:], + SwapHash: swapHash[:], + }, + ) + if err != nil { + return err + } + } + + return nil + }) +} + +// SwapHashesForDepositIDs retrieves the swap hashes for the given deposit IDs. +func (s *SqlStore) SwapHashesForDepositIDs(ctx context.Context, + depositIDs []deposit.ID) (map[lntypes.Hash][]deposit.ID, error) { + + swapHashes := make(map[lntypes.Hash][]deposit.ID) + for _, id := range depositIDs { + swapHash, err := s.baseDB.SwapHashForDepositID(ctx, id[:]) + if err != nil { + if errors.Is(err, sql.ErrNoRows) || + errors.Is(err, pgx.ErrNoRows) { + + return nil, nil + } + + return nil, err + } + + if swapHash == nil { + return nil, nil + } + + if len(swapHash) != lntypes.HashSize { + return nil, errors.New("invalid swap hash length") + } + + swapHashParsed, err := lntypes.MakeHash(swapHash) + if err != nil { + return nil, err + } + + // Place the deposit ID in the map under the + // corresponding swap hash. + swapHashes[swapHashParsed] = append( + swapHashes[swapHashParsed], id, + ) + } + + return swapHashes, nil +} + +// DepositIDsForSwapHash retrieves all deposit IDs for a given swap hash. +func (s *SqlStore) DepositIDsForSwapHash(ctx context.Context, + swapHash lntypes.Hash) ([]deposit.ID, error) { + + byteIDs, err := s.baseDB.DepositIDsForSwapHash(ctx, swapHash[:]) + if err != nil { + return nil, err + } + + depositIDs := make([]deposit.ID, len(byteIDs)) + for i, id := range byteIDs { + if len(id) != deposit.IdLength { + return nil, errors.New("invalid deposit ID length") + } + copy(depositIDs[i][:], id) + } + + return depositIDs, nil +} + // toStaticAddressLoopIn converts sql rows to an instant out struct. func toStaticAddressLoopIn(_ context.Context, network *chaincfg.Params, row sqlc.GetStaticAddressLoopInSwapRow,