mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-18 13:08:28 +02:00
Merge pull request #938 from hieblmi/withdraw-info
staticaddr: persist withdrawal info
This commit is contained in:
commit
80828c272c
20 changed files with 1855 additions and 639 deletions
|
|
@ -30,6 +30,7 @@ var staticAddressCommands = cli.Command{
|
|||
newStaticAddressCommand,
|
||||
listUnspentCommand,
|
||||
listDepositsCommand,
|
||||
listWithdrawalsCommand,
|
||||
listStaticAddressSwapsCommand,
|
||||
withdrawalCommand,
|
||||
summaryCommand,
|
||||
|
|
@ -312,6 +313,38 @@ func listDeposits(ctx *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var listWithdrawalsCommand = cli.Command{
|
||||
Name: "listwithdrawals",
|
||||
Usage: "Display a summary of past withdrawals.",
|
||||
Description: `
|
||||
`,
|
||||
Action: listWithdrawals,
|
||||
}
|
||||
|
||||
func listWithdrawals(ctx *cli.Context) error {
|
||||
ctxb := context.Background()
|
||||
if ctx.NArg() > 0 {
|
||||
return cli.ShowCommandHelp(ctx, "withdrawals")
|
||||
}
|
||||
|
||||
client, cleanup, err := getClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
resp, err := client.ListStaticAddressWithdrawals(
|
||||
ctxb, &looprpc.ListStaticAddressWithdrawalRequest{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printRespJSON(resp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var listStaticAddressSwapsCommand = cli.Command{
|
||||
Name: "listswaps",
|
||||
Usage: "Shows a list of finalized static address swaps.",
|
||||
|
|
|
|||
|
|
@ -604,6 +604,10 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
|
|||
depositManager = deposit.NewManager(depoCfg)
|
||||
|
||||
// Static address deposit withdrawal manager setup.
|
||||
withdrawalStore := withdraw.NewSqlStore(
|
||||
loopdb.NewTypedStore[withdraw.Querier](baseDb),
|
||||
depositStore,
|
||||
)
|
||||
withdrawalCfg := &withdraw.ManagerConfig{
|
||||
StaticAddressServerClient: staticAddressClient,
|
||||
AddressManager: staticAddressManager,
|
||||
|
|
@ -612,6 +616,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
|
|||
ChainParams: d.lnd.ChainParams,
|
||||
ChainNotifier: d.lnd.ChainNotifier,
|
||||
Signer: d.lnd.Signer,
|
||||
Store: withdrawalStore,
|
||||
}
|
||||
withdrawalManager = withdraw.NewManager(withdrawalCfg, blockHeight)
|
||||
|
||||
|
|
|
|||
|
|
@ -1673,6 +1673,53 @@ func (s *swapClientServer) ListStaticAddressDeposits(ctx context.Context,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// ListStaticAddressWithdrawals returns a list of all finalized withdrawal
|
||||
// transactions.
|
||||
func (s *swapClientServer) ListStaticAddressWithdrawals(ctx context.Context,
|
||||
_ *looprpc.ListStaticAddressWithdrawalRequest) (
|
||||
*looprpc.ListStaticAddressWithdrawalResponse, error) {
|
||||
|
||||
withdrawals, err := s.withdrawalManager.GetAllWithdrawals(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(withdrawals) == 0 {
|
||||
return &looprpc.ListStaticAddressWithdrawalResponse{}, nil
|
||||
}
|
||||
|
||||
clientWithdrawals := make(
|
||||
[]*looprpc.StaticAddressWithdrawal, 0, len(withdrawals),
|
||||
)
|
||||
for _, w := range withdrawals {
|
||||
deposits := make([]*looprpc.Deposit, 0, len(w.Deposits))
|
||||
for _, d := range w.Deposits {
|
||||
deposits = append(deposits, &looprpc.Deposit{
|
||||
Id: d.ID[:],
|
||||
Outpoint: d.OutPoint.String(),
|
||||
Value: int64(d.Value),
|
||||
ConfirmationHeight: d.ConfirmationHeight,
|
||||
State: toClientDepositState(
|
||||
d.GetState(),
|
||||
),
|
||||
})
|
||||
}
|
||||
withdrawal := &looprpc.StaticAddressWithdrawal{
|
||||
TxId: w.TxID.String(),
|
||||
Deposits: deposits,
|
||||
TotalDepositAmountSatoshis: int64(w.TotalDepositAmount),
|
||||
WithdrawnAmountSatoshis: int64(w.WithdrawnAmount),
|
||||
ChangeAmountSatoshis: int64(w.ChangeAmount),
|
||||
ConfirmationHeight: uint32(w.ConfirmationHeight),
|
||||
}
|
||||
clientWithdrawals = append(clientWithdrawals, withdrawal)
|
||||
}
|
||||
|
||||
return &looprpc.ListStaticAddressWithdrawalResponse{
|
||||
Withdrawals: clientWithdrawals,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListStaticAddressSwaps returns a list of all swaps that are currently pending
|
||||
// or previously succeeded.
|
||||
func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE IF EXISTS withdrawals;
|
||||
DROP TABLE IF EXISTS withdrawal_deposits;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
-- withdrawals stores finalized static address withdrawals.
|
||||
CREATE TABLE IF NOT EXISTS withdrawals (
|
||||
-- id is the auto-incrementing primary key for a withdrawal.
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
-- withdrawal_id is the unique identifier for the withdrawal.
|
||||
withdrawal_id BLOB NOT NULL UNIQUE,
|
||||
|
||||
-- withdrawal_tx_id is the transaction tx id of the withdrawal.
|
||||
withdrawal_tx_id TEXT UNIQUE,
|
||||
|
||||
-- total_deposit_amount is the total amount of the deposits in satoshis.
|
||||
total_deposit_amount BIGINT NOT NULL,
|
||||
|
||||
-- withdrawn_amount is the total amount of the withdrawal. It amounts
|
||||
-- to the total amount of the deposits minus the fees and optional change.
|
||||
withdrawn_amount BIGINT,
|
||||
|
||||
-- change_amount is the optional change that the user selected.
|
||||
change_amount BIGINT,
|
||||
|
||||
-- initiation_time is the creation of the withdrawal.
|
||||
initiation_time TIMESTAMP NOT NULL,
|
||||
|
||||
-- confirmation_height is the block height at which the withdrawal was first
|
||||
-- confirmed.
|
||||
confirmation_height BIGINT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS withdrawal_deposits (
|
||||
-- id is the auto-incrementing primary key.
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
-- withdrawal_id references the withdrawals table.
|
||||
withdrawal_id BLOB NOT NULL REFERENCES withdrawals(withdrawal_id),
|
||||
|
||||
-- deposit_id references the deposits table.
|
||||
deposit_id BLOB NOT NULL REFERENCES deposits(deposit_id),
|
||||
|
||||
-- Ensure that each deposit is used only once per withdrawal.
|
||||
UNIQUE(deposit_id, withdrawal_id)
|
||||
);
|
||||
|
|
@ -208,3 +208,20 @@ type SweepsOld struct {
|
|||
Amt int64
|
||||
Completed bool
|
||||
}
|
||||
|
||||
type Withdrawal struct {
|
||||
ID int32
|
||||
WithdrawalID []byte
|
||||
WithdrawalTxID sql.NullString
|
||||
TotalDepositAmount int64
|
||||
WithdrawnAmount sql.NullInt64
|
||||
ChangeAmount sql.NullInt64
|
||||
InitiationTime time.Time
|
||||
ConfirmationHeight sql.NullInt64
|
||||
}
|
||||
|
||||
type WithdrawalDeposit struct {
|
||||
ID int32
|
||||
WithdrawalID []byte
|
||||
DepositID []byte
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,11 @@ type Querier interface {
|
|||
CreateDeposit(ctx context.Context, arg CreateDepositParams) error
|
||||
CreateReservation(ctx context.Context, arg CreateReservationParams) error
|
||||
CreateStaticAddress(ctx context.Context, arg CreateStaticAddressParams) error
|
||||
CreateWithdrawal(ctx context.Context, arg CreateWithdrawalParams) error
|
||||
CreateWithdrawalDeposit(ctx context.Context, arg CreateWithdrawalDepositParams) error
|
||||
DropBatch(ctx context.Context, id int32) error
|
||||
FetchLiquidityParams(ctx context.Context) ([]byte, error)
|
||||
GetAllWithdrawals(ctx context.Context) ([]Withdrawal, error)
|
||||
GetBatchSweeps(ctx context.Context, batchID int32) ([]Sweep, error)
|
||||
GetBatchSweptAmount(ctx context.Context, batchID int32) (int64, error)
|
||||
GetDeposit(ctx context.Context, depositID []byte) (Deposit, error)
|
||||
|
|
@ -42,6 +45,8 @@ type Querier interface {
|
|||
GetSwapUpdates(ctx context.Context, swapHash []byte) ([]SwapUpdate, error)
|
||||
GetSweepStatus(ctx context.Context, outpoint string) (bool, error)
|
||||
GetUnconfirmedBatches(ctx context.Context) ([]SweepBatch, error)
|
||||
GetWithdrawalDeposits(ctx context.Context, withdrawalID []byte) ([][]byte, error)
|
||||
GetWithdrawalIDByDepositID(ctx context.Context, depositID []byte) ([]byte, error)
|
||||
InsertBatch(ctx context.Context, arg InsertBatchParams) (int32, error)
|
||||
InsertDepositUpdate(ctx context.Context, arg InsertDepositUpdateParams) error
|
||||
InsertHtlcKeys(ctx context.Context, arg InsertHtlcKeysParams) error
|
||||
|
|
@ -64,6 +69,7 @@ type Querier interface {
|
|||
UpdateLoopOutAssetOffchainPayments(ctx context.Context, arg UpdateLoopOutAssetOffchainPaymentsParams) error
|
||||
UpdateReservation(ctx context.Context, arg UpdateReservationParams) error
|
||||
UpdateStaticAddressLoopIn(ctx context.Context, arg UpdateStaticAddressLoopInParams) error
|
||||
UpdateWithdrawal(ctx context.Context, arg UpdateWithdrawalParams) error
|
||||
UpsertLiquidityParams(ctx context.Context, params []byte) error
|
||||
UpsertSweep(ctx context.Context, arg UpsertSweepParams) error
|
||||
}
|
||||
|
|
|
|||
47
loopdb/sqlc/queries/static_address_withdrawals.sql
Normal file
47
loopdb/sqlc/queries/static_address_withdrawals.sql
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
-- name: CreateWithdrawal :exec
|
||||
INSERT INTO withdrawals (
|
||||
withdrawal_id,
|
||||
total_deposit_amount,
|
||||
initiation_time
|
||||
) VALUES (
|
||||
$1, $2, $3
|
||||
);
|
||||
|
||||
-- name: CreateWithdrawalDeposit :exec
|
||||
INSERT INTO withdrawal_deposits (
|
||||
withdrawal_id,
|
||||
deposit_id
|
||||
) VALUES (
|
||||
$1, $2
|
||||
);
|
||||
|
||||
-- name: GetWithdrawalIDByDepositID :one
|
||||
SELECT withdrawal_id
|
||||
FROM withdrawal_deposits
|
||||
WHERE deposit_id = $1;
|
||||
|
||||
-- name: UpdateWithdrawal :exec
|
||||
UPDATE withdrawals
|
||||
SET
|
||||
withdrawal_tx_id = $2,
|
||||
withdrawn_amount = $3,
|
||||
change_amount = $4,
|
||||
confirmation_height = $5
|
||||
WHERE
|
||||
withdrawal_id = $1;
|
||||
|
||||
-- name: GetWithdrawalDeposits :many
|
||||
SELECT
|
||||
deposit_id
|
||||
FROM
|
||||
withdrawal_deposits
|
||||
WHERE
|
||||
withdrawal_id = $1;
|
||||
|
||||
-- name: GetAllWithdrawals :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
withdrawals
|
||||
ORDER BY
|
||||
initiation_time DESC;
|
||||
168
loopdb/sqlc/static_address_withdrawals.sql.go
Normal file
168
loopdb/sqlc/static_address_withdrawals.sql.go
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.25.0
|
||||
// source: static_address_withdrawals.sql
|
||||
|
||||
package sqlc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
const createWithdrawal = `-- name: CreateWithdrawal :exec
|
||||
INSERT INTO withdrawals (
|
||||
withdrawal_id,
|
||||
total_deposit_amount,
|
||||
initiation_time
|
||||
) VALUES (
|
||||
$1, $2, $3
|
||||
)
|
||||
`
|
||||
|
||||
type CreateWithdrawalParams struct {
|
||||
WithdrawalID []byte
|
||||
TotalDepositAmount int64
|
||||
InitiationTime time.Time
|
||||
}
|
||||
|
||||
func (q *Queries) CreateWithdrawal(ctx context.Context, arg CreateWithdrawalParams) error {
|
||||
_, err := q.db.ExecContext(ctx, createWithdrawal, arg.WithdrawalID, arg.TotalDepositAmount, arg.InitiationTime)
|
||||
return err
|
||||
}
|
||||
|
||||
const createWithdrawalDeposit = `-- name: CreateWithdrawalDeposit :exec
|
||||
INSERT INTO withdrawal_deposits (
|
||||
withdrawal_id,
|
||||
deposit_id
|
||||
) VALUES (
|
||||
$1, $2
|
||||
)
|
||||
`
|
||||
|
||||
type CreateWithdrawalDepositParams struct {
|
||||
WithdrawalID []byte
|
||||
DepositID []byte
|
||||
}
|
||||
|
||||
func (q *Queries) CreateWithdrawalDeposit(ctx context.Context, arg CreateWithdrawalDepositParams) error {
|
||||
_, err := q.db.ExecContext(ctx, createWithdrawalDeposit, arg.WithdrawalID, arg.DepositID)
|
||||
return err
|
||||
}
|
||||
|
||||
const getAllWithdrawals = `-- name: GetAllWithdrawals :many
|
||||
SELECT
|
||||
id, withdrawal_id, withdrawal_tx_id, total_deposit_amount, withdrawn_amount, change_amount, initiation_time, confirmation_height
|
||||
FROM
|
||||
withdrawals
|
||||
ORDER BY
|
||||
initiation_time DESC
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllWithdrawals(ctx context.Context) ([]Withdrawal, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getAllWithdrawals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Withdrawal
|
||||
for rows.Next() {
|
||||
var i Withdrawal
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.WithdrawalID,
|
||||
&i.WithdrawalTxID,
|
||||
&i.TotalDepositAmount,
|
||||
&i.WithdrawnAmount,
|
||||
&i.ChangeAmount,
|
||||
&i.InitiationTime,
|
||||
&i.ConfirmationHeight,
|
||||
); 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 getWithdrawalDeposits = `-- name: GetWithdrawalDeposits :many
|
||||
SELECT
|
||||
deposit_id
|
||||
FROM
|
||||
withdrawal_deposits
|
||||
WHERE
|
||||
withdrawal_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetWithdrawalDeposits(ctx context.Context, withdrawalID []byte) ([][]byte, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWithdrawalDeposits, withdrawalID)
|
||||
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 getWithdrawalIDByDepositID = `-- name: GetWithdrawalIDByDepositID :one
|
||||
SELECT withdrawal_id
|
||||
FROM withdrawal_deposits
|
||||
WHERE deposit_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetWithdrawalIDByDepositID(ctx context.Context, depositID []byte) ([]byte, error) {
|
||||
row := q.db.QueryRowContext(ctx, getWithdrawalIDByDepositID, depositID)
|
||||
var withdrawal_id []byte
|
||||
err := row.Scan(&withdrawal_id)
|
||||
return withdrawal_id, err
|
||||
}
|
||||
|
||||
const updateWithdrawal = `-- name: UpdateWithdrawal :exec
|
||||
UPDATE withdrawals
|
||||
SET
|
||||
withdrawal_tx_id = $2,
|
||||
withdrawn_amount = $3,
|
||||
change_amount = $4,
|
||||
confirmation_height = $5
|
||||
WHERE
|
||||
withdrawal_id = $1
|
||||
`
|
||||
|
||||
type UpdateWithdrawalParams struct {
|
||||
WithdrawalID []byte
|
||||
WithdrawalTxID sql.NullString
|
||||
WithdrawnAmount sql.NullInt64
|
||||
ChangeAmount sql.NullInt64
|
||||
ConfirmationHeight sql.NullInt64
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateWithdrawal(ctx context.Context, arg UpdateWithdrawalParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateWithdrawal,
|
||||
arg.WithdrawalID,
|
||||
arg.WithdrawalTxID,
|
||||
arg.WithdrawnAmount,
|
||||
arg.ChangeAmount,
|
||||
arg.ConfirmationHeight,
|
||||
)
|
||||
return err
|
||||
}
|
||||
1495
looprpc/client.pb.go
1495
looprpc/client.pb.go
File diff suppressed because it is too large
Load diff
|
|
@ -175,6 +175,12 @@ service SwapClient {
|
|||
rpc ListStaticAddressDeposits (ListStaticAddressDepositsRequest)
|
||||
returns (ListStaticAddressDepositsResponse);
|
||||
|
||||
/* loop:`listwithdrawals`
|
||||
ListStaticAddressWithdrawals returns a list of static address withdrawals.
|
||||
*/
|
||||
rpc ListStaticAddressWithdrawals (ListStaticAddressWithdrawalRequest)
|
||||
returns (ListStaticAddressWithdrawalResponse);
|
||||
|
||||
/* loop:`listswaps`
|
||||
ListStaticAddressSwaps returns a list of filtered static address
|
||||
swaps.
|
||||
|
|
@ -1735,6 +1741,16 @@ message ListStaticAddressDepositsResponse {
|
|||
repeated Deposit filtered_deposits = 1;
|
||||
}
|
||||
|
||||
message ListStaticAddressWithdrawalRequest {
|
||||
}
|
||||
|
||||
message ListStaticAddressWithdrawalResponse {
|
||||
/*
|
||||
A list of all static address withdrawals.
|
||||
*/
|
||||
repeated StaticAddressWithdrawal withdrawals = 1;
|
||||
}
|
||||
|
||||
message ListStaticAddressSwapsRequest {
|
||||
}
|
||||
|
||||
|
|
@ -1896,6 +1912,40 @@ message Deposit {
|
|||
int64 blocks_until_expiry = 6;
|
||||
}
|
||||
|
||||
message StaticAddressWithdrawal {
|
||||
/*
|
||||
The transaction id of the withdrawal transaction.
|
||||
*/
|
||||
string tx_id = 1;
|
||||
|
||||
/*
|
||||
The selected deposits that is withdrawn from.
|
||||
*/
|
||||
repeated Deposit deposits = 2;
|
||||
|
||||
/*
|
||||
The sum of the deposit values that was selected for withdrawal.
|
||||
*/
|
||||
int64 total_deposit_amount_satoshis = 3;
|
||||
|
||||
/*
|
||||
The actual amount that was withdrawn from the selected deposits. This value
|
||||
represents the sum of selected deposit values minus tx fees minus optional
|
||||
change output.
|
||||
*/
|
||||
int64 withdrawn_amount_satoshis = 4;
|
||||
|
||||
/*
|
||||
An optional change.
|
||||
*/
|
||||
int64 change_amount_satoshis = 5;
|
||||
|
||||
/*
|
||||
The confirmation block height of the withdrawal transaction.
|
||||
*/
|
||||
uint32 confirmation_height = 6;
|
||||
}
|
||||
|
||||
message StaticAddressLoopInSwap {
|
||||
/*
|
||||
The swap hash of the swap. It represents the unique identifier of the swap.
|
||||
|
|
|
|||
|
|
@ -1392,6 +1392,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"looprpcListStaticAddressWithdrawalResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"withdrawals": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/looprpcStaticAddressWithdrawal"
|
||||
},
|
||||
"description": "A list of all static address withdrawals."
|
||||
}
|
||||
}
|
||||
},
|
||||
"looprpcListSwapsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1927,6 +1939,42 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"looprpcStaticAddressWithdrawal": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tx_id": {
|
||||
"type": "string",
|
||||
"description": "The transaction id of the withdrawal transaction."
|
||||
},
|
||||
"deposits": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/looprpcDeposit"
|
||||
},
|
||||
"description": "The selected deposits that is withdrawn from."
|
||||
},
|
||||
"total_deposit_amount_satoshis": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "The sum of the deposit values that was selected for withdrawal."
|
||||
},
|
||||
"withdrawn_amount_satoshis": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "The actual amount that was withdrawn from the selected deposits. This value\nrepresents the sum of selected deposit values minus tx fees minus optional\nchange output."
|
||||
},
|
||||
"change_amount_satoshis": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "An optional change."
|
||||
},
|
||||
"confirmation_height": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "The confirmation block height of the withdrawal transaction."
|
||||
}
|
||||
}
|
||||
},
|
||||
"looprpcSuggestSwapsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -119,6 +119,9 @@ type SwapClientClient interface {
|
|||
// ListStaticAddressDeposits returns a list of filtered static address
|
||||
// deposits.
|
||||
ListStaticAddressDeposits(ctx context.Context, in *ListStaticAddressDepositsRequest, opts ...grpc.CallOption) (*ListStaticAddressDepositsResponse, error)
|
||||
// loop:`listwithdrawals`
|
||||
// ListStaticAddressWithdrawals returns a list of static address withdrawals.
|
||||
ListStaticAddressWithdrawals(ctx context.Context, in *ListStaticAddressWithdrawalRequest, opts ...grpc.CallOption) (*ListStaticAddressWithdrawalResponse, error)
|
||||
// loop:`listswaps`
|
||||
// ListStaticAddressSwaps returns a list of filtered static address
|
||||
// swaps.
|
||||
|
|
@ -397,6 +400,15 @@ func (c *swapClientClient) ListStaticAddressDeposits(ctx context.Context, in *Li
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *swapClientClient) ListStaticAddressWithdrawals(ctx context.Context, in *ListStaticAddressWithdrawalRequest, opts ...grpc.CallOption) (*ListStaticAddressWithdrawalResponse, error) {
|
||||
out := new(ListStaticAddressWithdrawalResponse)
|
||||
err := c.cc.Invoke(ctx, "/looprpc.SwapClient/ListStaticAddressWithdrawals", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *swapClientClient) ListStaticAddressSwaps(ctx context.Context, in *ListStaticAddressSwapsRequest, opts ...grpc.CallOption) (*ListStaticAddressSwapsResponse, error) {
|
||||
out := new(ListStaticAddressSwapsResponse)
|
||||
err := c.cc.Invoke(ctx, "/looprpc.SwapClient/ListStaticAddressSwaps", in, out, opts...)
|
||||
|
|
@ -529,6 +541,9 @@ type SwapClientServer interface {
|
|||
// ListStaticAddressDeposits returns a list of filtered static address
|
||||
// deposits.
|
||||
ListStaticAddressDeposits(context.Context, *ListStaticAddressDepositsRequest) (*ListStaticAddressDepositsResponse, error)
|
||||
// loop:`listwithdrawals`
|
||||
// ListStaticAddressWithdrawals returns a list of static address withdrawals.
|
||||
ListStaticAddressWithdrawals(context.Context, *ListStaticAddressWithdrawalRequest) (*ListStaticAddressWithdrawalResponse, error)
|
||||
// loop:`listswaps`
|
||||
// ListStaticAddressSwaps returns a list of filtered static address
|
||||
// swaps.
|
||||
|
|
@ -625,6 +640,9 @@ func (UnimplementedSwapClientServer) WithdrawDeposits(context.Context, *Withdraw
|
|||
func (UnimplementedSwapClientServer) ListStaticAddressDeposits(context.Context, *ListStaticAddressDepositsRequest) (*ListStaticAddressDepositsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListStaticAddressDeposits not implemented")
|
||||
}
|
||||
func (UnimplementedSwapClientServer) ListStaticAddressWithdrawals(context.Context, *ListStaticAddressWithdrawalRequest) (*ListStaticAddressWithdrawalResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListStaticAddressWithdrawals not implemented")
|
||||
}
|
||||
func (UnimplementedSwapClientServer) ListStaticAddressSwaps(context.Context, *ListStaticAddressSwapsRequest) (*ListStaticAddressSwapsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListStaticAddressSwaps not implemented")
|
||||
}
|
||||
|
|
@ -1118,6 +1136,24 @@ func _SwapClient_ListStaticAddressDeposits_Handler(srv interface{}, ctx context.
|
|||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SwapClient_ListStaticAddressWithdrawals_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListStaticAddressWithdrawalRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SwapClientServer).ListStaticAddressWithdrawals(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/looprpc.SwapClient/ListStaticAddressWithdrawals",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SwapClientServer).ListStaticAddressWithdrawals(ctx, req.(*ListStaticAddressWithdrawalRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SwapClient_ListStaticAddressSwaps_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListStaticAddressSwapsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
|
|
@ -1279,6 +1315,10 @@ var SwapClient_ServiceDesc = grpc.ServiceDesc{
|
|||
MethodName: "ListStaticAddressDeposits",
|
||||
Handler: _SwapClient_ListStaticAddressDeposits_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListStaticAddressWithdrawals",
|
||||
Handler: _SwapClient_ListStaticAddressWithdrawals_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListStaticAddressSwaps",
|
||||
Handler: _SwapClient_ListStaticAddressSwaps_Handler,
|
||||
|
|
|
|||
|
|
@ -101,6 +101,13 @@ var RequiredPermissions = map[string][]bakery.Op{
|
|||
Entity: "loop",
|
||||
Action: "in",
|
||||
}},
|
||||
"/looprpc.SwapClient/ListStaticAddressWithdrawals": {{
|
||||
Entity: "swap",
|
||||
Action: "read",
|
||||
}, {
|
||||
Entity: "loop",
|
||||
Action: "in",
|
||||
}},
|
||||
"/looprpc.SwapClient/ListStaticAddressSwaps": {{
|
||||
Entity: "swap",
|
||||
Action: "read",
|
||||
|
|
|
|||
|
|
@ -688,6 +688,31 @@ func RegisterSwapClientJSONCallbacks(registry map[string]func(ctx context.Contex
|
|||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["looprpc.SwapClient.ListStaticAddressWithdrawals"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
req := &ListStaticAddressWithdrawalRequest{}
|
||||
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := NewSwapClientClient(conn)
|
||||
resp, err := client.ListStaticAddressWithdrawals(ctx, req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
respBytes, err := marshaler.Marshal(resp)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["looprpc.SwapClient.ListStaticAddressSwaps"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,18 @@ import (
|
|||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
// Store is the database interface that is used to store and retrieve
|
||||
// static address withdrawals.
|
||||
type Store interface {
|
||||
// CreateWithdrawal inserts a withdrawal into the store.
|
||||
CreateWithdrawal(ctx context.Context, tx *wire.MsgTx,
|
||||
confirmationHeight uint32, deposits []*deposit.Deposit,
|
||||
changePkScript []byte) error
|
||||
|
||||
// GetAllWithdrawals retrieves all withdrawals.
|
||||
GetAllWithdrawals(ctx context.Context) ([]Withdrawal, error)
|
||||
}
|
||||
|
||||
// AddressManager handles fetching of address parameters.
|
||||
type AddressManager interface {
|
||||
// GetStaticAddressParameters returns the static address parameters.
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@ type ManagerConfig struct {
|
|||
|
||||
// Signer is the signer client that is used to sign transactions.
|
||||
Signer lndclient.SignerClient
|
||||
|
||||
// Store is the store that is used to persist the finalized withdrawal
|
||||
// transactions.
|
||||
Store *SqlStore
|
||||
}
|
||||
|
||||
// newWithdrawalRequest is used to send withdrawal request to the manager main
|
||||
|
|
@ -401,6 +405,13 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
|
|||
// republished in case of a fee bump, it suffices if only one spent
|
||||
// notifier is run.
|
||||
if allDeposited {
|
||||
// Persist info about the finalized withdrawal.
|
||||
err = m.cfg.Store.CreateWithdrawal(ctx, deposits)
|
||||
if err != nil {
|
||||
log.Errorf("Error persisting "+
|
||||
"withdrawal: %v", err)
|
||||
}
|
||||
|
||||
err = m.handleWithdrawal(
|
||||
ctx, deposits, finalizedTx.TxHash(), withdrawalPkScript,
|
||||
)
|
||||
|
|
@ -592,46 +603,35 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
|
|||
deposits []*deposit.Deposit, txHash chainhash.Hash,
|
||||
withdrawalPkscript []byte) error {
|
||||
|
||||
staticAddress, err := m.cfg.AddressManager.GetStaticAddress(ctx)
|
||||
addrParams, err := m.cfg.AddressManager.GetStaticAddressParameters(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("error retrieving taproot address %w", err)
|
||||
log.Errorf("error retrieving address params %w", err)
|
||||
|
||||
return fmt.Errorf("withdrawal failed")
|
||||
}
|
||||
|
||||
address, err := btcutil.NewAddressTaproot(
|
||||
schnorr.SerializePubKey(staticAddress.TaprootKey),
|
||||
m.cfg.ChainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
script, err := txscript.PayToAddrScript(address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d := deposits[0]
|
||||
spentChan, errChan, err := m.cfg.ChainNotifier.RegisterSpendNtfn(
|
||||
ctx, &d.OutPoint, script, int32(d.ConfirmationHeight),
|
||||
ctx, &d.OutPoint, addrParams.PkScript,
|
||||
int32(d.ConfirmationHeight),
|
||||
)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-spentChan:
|
||||
case spentTx := <-spentChan:
|
||||
spendingHeight := uint32(spentTx.SpendingHeight)
|
||||
// If the transaction received one confirmation, we
|
||||
// ensure re-org safety by waiting for some more
|
||||
// confirmations.
|
||||
var confChan chan *chainntnfs.TxConfirmation
|
||||
confChan, errChan, err =
|
||||
m.cfg.ChainNotifier.RegisterConfirmationsNtfn(
|
||||
ctx, &txHash, withdrawalPkscript,
|
||||
MinConfs,
|
||||
ctx, spentTx.SpenderTxHash,
|
||||
withdrawalPkscript, MinConfs,
|
||||
int32(m.initiationHeight.Load()),
|
||||
)
|
||||
select {
|
||||
case <-confChan:
|
||||
case tx := <-confChan:
|
||||
err = m.cfg.DepositManager.TransitionDeposits(
|
||||
ctx, deposits, deposit.OnWithdrawn,
|
||||
deposit.Withdrawn,
|
||||
|
|
@ -641,12 +641,23 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
|
|||
"deposits: %v", err)
|
||||
}
|
||||
|
||||
// Remove the withdrawal tx from the active withdrawals
|
||||
// to stop republishing it on block arrivals.
|
||||
// Remove the withdrawal tx from the active
|
||||
// withdrawals to stop republishing it on block
|
||||
// arrivals.
|
||||
m.mu.Lock()
|
||||
delete(m.finalizedWithdrawalTxns, txHash)
|
||||
m.mu.Unlock()
|
||||
|
||||
// Persist info about the finalized withdrawal.
|
||||
err = m.cfg.Store.UpdateWithdrawal(
|
||||
ctx, deposits, tx.Tx, spendingHeight,
|
||||
addrParams.PkScript,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Error persisting "+
|
||||
"withdrawal: %v", err)
|
||||
}
|
||||
|
||||
case err := <-errChan:
|
||||
log.Errorf("Error waiting for confirmation: %v",
|
||||
err)
|
||||
|
|
@ -1126,3 +1137,8 @@ func (m *Manager) DeliverWithdrawalRequest(ctx context.Context,
|
|||
"for withdrawal response")
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllWithdrawals returns all finalized withdrawals from the store.
|
||||
func (m *Manager) GetAllWithdrawals(ctx context.Context) ([]Withdrawal, error) {
|
||||
return m.cfg.Store.GetAllWithdrawals(ctx)
|
||||
}
|
||||
|
|
|
|||
221
staticaddr/withdraw/sql_store.go
Normal file
221
staticaddr/withdraw/sql_store.go
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
package withdraw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"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/lightningnetwork/lnd/clock"
|
||||
)
|
||||
|
||||
type Querier interface {
|
||||
// CreateWithdrawal inserts a new withdrawal.
|
||||
CreateWithdrawal(ctx context.Context,
|
||||
arg sqlc.CreateWithdrawalParams) error
|
||||
|
||||
// UpdateWithdrawal updates a withdrawal with confirmation parameters.
|
||||
UpdateWithdrawal(ctx context.Context,
|
||||
arg sqlc.UpdateWithdrawalParams) error
|
||||
|
||||
// GetWithdrawalIDByDepositID retrieves the withdrawal ID associated
|
||||
// with a given deposit ID.
|
||||
GetWithdrawalIDByDepositID(ctx context.Context, depositID []byte) (
|
||||
[]byte, error)
|
||||
|
||||
// CreateWithdrawalDeposit links withdrawal to deposits.
|
||||
CreateWithdrawalDeposit(ctx context.Context,
|
||||
arg sqlc.CreateWithdrawalDepositParams) error
|
||||
|
||||
// GetWithdrawalDeposits retrieves the deposit IDs associated with a
|
||||
// withdrawal.
|
||||
GetWithdrawalDeposits(ctx context.Context, withdrawalID []byte) (
|
||||
[][]byte, error)
|
||||
|
||||
// GetAllWithdrawals retrieves all withdrawals from the database.
|
||||
GetAllWithdrawals(ctx context.Context) ([]sqlc.Withdrawal, error)
|
||||
}
|
||||
|
||||
// BaseDB is the interface that contains all the queries generated by sqlc for
|
||||
// the static_address_swaps table and transaction functionality.
|
||||
type BaseDB interface {
|
||||
Querier
|
||||
|
||||
// ExecTx allows for executing a function in the context of a database
|
||||
// transaction.
|
||||
ExecTx(ctx context.Context, txOptions loopdb.TxOptions,
|
||||
txBody func(Querier) error) error
|
||||
}
|
||||
|
||||
// SqlStore is the backing store for static address withdrawals.
|
||||
type SqlStore struct {
|
||||
baseDB BaseDB
|
||||
depositStore deposit.Store
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// NewSqlStore constructs a new SQLStore from a BaseDB. The BaseDB is agnostic
|
||||
// to the underlying driver which can be postgres or sqlite.
|
||||
func NewSqlStore(db BaseDB, depositStore deposit.Store) *SqlStore {
|
||||
return &SqlStore{
|
||||
baseDB: db,
|
||||
depositStore: depositStore,
|
||||
clock: clock.NewDefaultClock(),
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWithdrawal creates a static address withdrawal record in the database.
|
||||
func (s *SqlStore) CreateWithdrawal(ctx context.Context,
|
||||
deposits []*deposit.Deposit) error {
|
||||
|
||||
id, err := GetRandomWithdrawalID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var totalAmount btcutil.Amount
|
||||
for _, deposit := range deposits {
|
||||
totalAmount += deposit.Value
|
||||
}
|
||||
|
||||
createArgs := sqlc.CreateWithdrawalParams{
|
||||
WithdrawalID: id[:],
|
||||
TotalDepositAmount: int64(totalAmount),
|
||||
InitiationTime: s.clock.Now().UTC(),
|
||||
}
|
||||
return s.baseDB.ExecTx(ctx, &loopdb.SqliteTxOptions{},
|
||||
func(q Querier) error {
|
||||
err := q.CreateWithdrawal(ctx, createArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, deposit := range deposits {
|
||||
err = q.CreateWithdrawalDeposit(
|
||||
ctx, sqlc.CreateWithdrawalDepositParams{
|
||||
WithdrawalID: id[:],
|
||||
DepositID: deposit.ID[:],
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateWithdrawal updates a withdrawal record with the transaction
|
||||
// information, including the withdrawn amount, change amount, and
|
||||
// confirmation height. It is expected that the withdrawal has already been
|
||||
// created with CreateWithdrawal, and that the deposits slice contains the
|
||||
// deposits associated with the withdrawal.
|
||||
func (s *SqlStore) UpdateWithdrawal(ctx context.Context,
|
||||
deposits []*deposit.Deposit, tx *wire.MsgTx, confirmationHeight uint32,
|
||||
changePkScript []byte) error {
|
||||
|
||||
// Populate the optional change amount.
|
||||
withdrawnAmount, changeAmount := int64(0), int64(0)
|
||||
if len(tx.TxOut) == 1 {
|
||||
withdrawnAmount = tx.TxOut[0].Value
|
||||
} else if len(tx.TxOut) == 2 {
|
||||
withdrawnAmount, changeAmount = tx.TxOut[0].Value, tx.TxOut[1].Value
|
||||
if bytes.Equal(changePkScript, tx.TxOut[0].PkScript) {
|
||||
changeAmount = tx.TxOut[0].Value
|
||||
withdrawnAmount = tx.TxOut[1].Value
|
||||
}
|
||||
}
|
||||
|
||||
updateArgs := sqlc.UpdateWithdrawalParams{
|
||||
WithdrawalTxID: sql.NullString{
|
||||
String: tx.TxHash().String(),
|
||||
Valid: true,
|
||||
},
|
||||
WithdrawnAmount: sql.NullInt64{
|
||||
Int64: withdrawnAmount,
|
||||
Valid: withdrawnAmount > 0,
|
||||
},
|
||||
ChangeAmount: sql.NullInt64{
|
||||
Int64: changeAmount,
|
||||
Valid: changeAmount > 0,
|
||||
},
|
||||
ConfirmationHeight: sql.NullInt64{
|
||||
Int64: int64(confirmationHeight),
|
||||
Valid: confirmationHeight > 0,
|
||||
},
|
||||
}
|
||||
|
||||
return s.baseDB.ExecTx(ctx, &loopdb.SqliteTxOptions{},
|
||||
func(q Querier) error {
|
||||
withdrawalID, err := q.GetWithdrawalIDByDepositID(
|
||||
ctx, deposits[0].ID[:],
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateArgs.WithdrawalID = withdrawalID
|
||||
err = q.UpdateWithdrawal(ctx, updateArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetAllWithdrawals retrieves all static address withdrawals from the
|
||||
// database. It returns a slice of Withdrawal structs, each containing a list
|
||||
// of associated deposits.
|
||||
func (s *SqlStore) GetAllWithdrawals(ctx context.Context) ([]Withdrawal,
|
||||
error) {
|
||||
|
||||
withdrawals, err := s.baseDB.GetAllWithdrawals(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]Withdrawal, 0, len(withdrawals))
|
||||
for _, w := range withdrawals {
|
||||
depositIDs, err := s.baseDB.GetWithdrawalDeposits(ctx,
|
||||
w.WithdrawalID)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deposits := make([]*deposit.Deposit, 0, len(depositIDs))
|
||||
for _, dID := range depositIDs {
|
||||
deposit, err := s.depositStore.GetDeposit(
|
||||
ctx, deposit.ID(dID),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deposits = append(deposits, deposit)
|
||||
}
|
||||
|
||||
txID, err := chainhash.NewHashFromStr(w.WithdrawalTxID.String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, Withdrawal{
|
||||
ID: ID(w.WithdrawalID),
|
||||
TxID: *txID,
|
||||
Deposits: deposits,
|
||||
TotalDepositAmount: btcutil.Amount(w.TotalDepositAmount),
|
||||
WithdrawnAmount: btcutil.Amount(w.WithdrawnAmount.Int64),
|
||||
ChangeAmount: btcutil.Amount(w.ChangeAmount.Int64),
|
||||
InitiationTime: w.InitiationTime,
|
||||
ConfirmationHeight: w.ConfirmationHeight.Int64,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
101
staticaddr/withdraw/sql_store_test.go
Normal file
101
staticaddr/withdraw/sql_store_test.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package withdraw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestSqlStore tests the basic functionality of the SQLStore.
|
||||
func TestSqlStore(t *testing.T) {
|
||||
ctxb := context.Background()
|
||||
testDb := loopdb.NewTestDB(t)
|
||||
defer testDb.Close()
|
||||
|
||||
depositStore := deposit.NewSqlStore(testDb.BaseDB)
|
||||
store := NewSqlStore(loopdb.NewTypedStore[Querier](testDb), depositStore)
|
||||
|
||||
newID := func() deposit.ID {
|
||||
did, err := deposit.GetRandomDepositID()
|
||||
require.NoError(t, err)
|
||||
|
||||
return did
|
||||
}
|
||||
|
||||
d1, d2 := &deposit.Deposit{
|
||||
ID: newID(),
|
||||
Value: btcutil.Amount(100_000),
|
||||
TimeOutSweepPkScript: []byte{
|
||||
0x00, 0x14, 0x1a, 0x2b, 0x3c, 0x41,
|
||||
},
|
||||
},
|
||||
&deposit.Deposit{
|
||||
ID: newID(),
|
||||
Value: btcutil.Amount(200_000),
|
||||
TimeOutSweepPkScript: []byte{
|
||||
0x00, 0x14, 0x1a, 0x2b, 0x3c, 0x4d,
|
||||
},
|
||||
}
|
||||
|
||||
withdrawalTx := &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
Value: int64(d1.Value + d2.Value - 100),
|
||||
PkScript: []byte{
|
||||
0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: int64(100),
|
||||
PkScript: []byte{
|
||||
0x01,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := depositStore.CreateDeposit(ctxb, d1)
|
||||
require.NoError(t, err)
|
||||
err = depositStore.CreateDeposit(ctxb, d2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.CreateWithdrawal(ctxb, []*deposit.Deposit{d1, d2})
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawals, err := store.GetAllWithdrawals(ctxb)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, withdrawals, 1)
|
||||
require.NotEmpty(t, withdrawals[0].ID)
|
||||
require.EqualValues(
|
||||
t, d1.Value+d2.Value, withdrawals[0].TotalDepositAmount,
|
||||
)
|
||||
require.Len(t, withdrawals[0].Deposits, 2)
|
||||
require.EqualValues(
|
||||
t, d1.Value, withdrawals[0].Deposits[0].Value,
|
||||
)
|
||||
require.EqualValues(
|
||||
t, d2.Value, withdrawals[0].Deposits[1].Value,
|
||||
)
|
||||
require.NotEmpty(t, withdrawals[0].InitiationTime)
|
||||
|
||||
err = store.UpdateWithdrawal(
|
||||
ctxb, []*deposit.Deposit{d1, d2}, withdrawalTx, 6, []byte{0x01},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawals, err = store.GetAllWithdrawals(ctxb)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, withdrawals, 1)
|
||||
require.NotEmpty(t, withdrawals[0].TxID)
|
||||
require.EqualValues(
|
||||
t, d1.Value+d2.Value-100, withdrawals[0].WithdrawnAmount,
|
||||
)
|
||||
require.EqualValues(t, 100, withdrawals[0].ChangeAmount)
|
||||
require.EqualValues(t, 6, withdrawals[0].ConfirmationHeight)
|
||||
}
|
||||
68
staticaddr/withdraw/withdrawal.go
Normal file
68
staticaddr/withdraw/withdrawal.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package withdraw
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
)
|
||||
|
||||
const (
|
||||
IdLength = 32
|
||||
)
|
||||
|
||||
// ID is a unique identifier for a deposit.
|
||||
type ID [IdLength]byte
|
||||
|
||||
// FromByteSlice creates a deposit id from a byte slice.
|
||||
func (r *ID) FromByteSlice(b []byte) error {
|
||||
if len(b) != IdLength {
|
||||
return fmt.Errorf("withdrawal id must be 32 bytes, got %d, %x",
|
||||
len(b), b)
|
||||
}
|
||||
|
||||
copy(r[:], b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Withdrawal represents a finalized static address withdrawal record in the
|
||||
// database.
|
||||
type Withdrawal struct {
|
||||
// ID is the unique identifier of the deposit.
|
||||
ID ID
|
||||
|
||||
// TxID is the transaction ID of the withdrawal.
|
||||
TxID chainhash.Hash
|
||||
|
||||
// Deposits is a list of deposits used to fund the withdrawal.
|
||||
Deposits []*deposit.Deposit
|
||||
|
||||
// TotalDepositAmount is the total amount of all deposits used to fund
|
||||
// the withdrawal.
|
||||
TotalDepositAmount btcutil.Amount
|
||||
|
||||
// WithdrawnAmount is the amount withdrawn. It represents the total
|
||||
// value of selected deposits minus fees and change.
|
||||
WithdrawnAmount btcutil.Amount
|
||||
|
||||
// ChangeAmount is the optional change returned to the static address.
|
||||
ChangeAmount btcutil.Amount
|
||||
|
||||
// InitiationTime is the time at which the withdrawal was initiated.
|
||||
InitiationTime time.Time
|
||||
|
||||
// ConfirmationHeight is the block height at which the withdrawal was
|
||||
// confirmed.
|
||||
ConfirmationHeight int64
|
||||
}
|
||||
|
||||
// GetRandomWithdrawalID generates a random withdrawal ID.
|
||||
func GetRandomWithdrawalID() (ID, error) {
|
||||
var id ID
|
||||
_, err := rand.Read(id[:])
|
||||
return id, err
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue