Skip to content

Commit 2190b80

Browse files
committed
staticaddr: retain historic withdrawal info
1 parent e701382 commit 2190b80

File tree

6 files changed

+248
-4
lines changed

6 files changed

+248
-4
lines changed

cmd/loop/staticaddr.go

+33
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ var staticAddressCommands = cli.Command{
3030
newStaticAddressCommand,
3131
listUnspentCommand,
3232
listDepositsCommand,
33+
listWithdrawalsCommand,
3334
listStaticAddressSwapsCommand,
3435
withdrawalCommand,
3536
summaryCommand,
@@ -247,6 +248,14 @@ var listDepositsCommand = cli.Command{
247248
Action: listDeposits,
248249
}
249250

251+
var listWithdrawalsCommand = cli.Command{
252+
Name: "listwithdrawals",
253+
Usage: "Display a summary of past withdrawals.",
254+
Description: `
255+
`,
256+
Action: listWithdrawals,
257+
}
258+
250259
var listStaticAddressSwapsCommand = cli.Command{
251260
Name: "listswaps",
252261
Usage: "Display a summary of static address related information.",
@@ -345,6 +354,30 @@ func listDeposits(ctx *cli.Context) error {
345354
return nil
346355
}
347356

357+
func listWithdrawals(ctx *cli.Context) error {
358+
ctxb := context.Background()
359+
if ctx.NArg() > 0 {
360+
return cli.ShowCommandHelp(ctx, "withdrawals")
361+
}
362+
363+
client, cleanup, err := getClient(ctx)
364+
if err != nil {
365+
return err
366+
}
367+
defer cleanup()
368+
369+
resp, err := client.ListStaticAddressWithdrawals(
370+
ctxb, &looprpc.ListStaticAddressWithdrawalRequest{},
371+
)
372+
if err != nil {
373+
return err
374+
}
375+
376+
printRespJSON(resp)
377+
378+
return nil
379+
}
380+
348381
func listStaticAddressSwaps(ctx *cli.Context) error {
349382
ctxb := context.Background()
350383
if ctx.NArg() > 0 {

loopd/daemon.go

+2
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
604604
depositManager = deposit.NewManager(depoCfg)
605605

606606
// Static address deposit withdrawal manager setup.
607+
withdrawalStore := withdraw.NewSqlStore(baseDb)
607608
withdrawalCfg := &withdraw.ManagerConfig{
608609
StaticAddressServerClient: staticAddressClient,
609610
AddressManager: staticAddressManager,
@@ -612,6 +613,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
612613
ChainParams: d.lnd.ChainParams,
613614
ChainNotifier: d.lnd.ChainNotifier,
614615
Signer: d.lnd.Signer,
616+
Store: withdrawalStore,
615617
}
616618
withdrawalManager = withdraw.NewManager(withdrawalCfg, blockHeight)
617619

loopd/swapclient_server.go

+35
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,41 @@ func (s *swapClientServer) ListStaticAddressDeposits(ctx context.Context,
16651665
}, nil
16661666
}
16671667

1668+
// ListStaticAddressWithdrawals returns a list of all finalized withdrawal
1669+
// transactions.
1670+
func (s *swapClientServer) ListStaticAddressWithdrawals(ctx context.Context,
1671+
_ *looprpc.ListStaticAddressWithdrawalRequest) (
1672+
*looprpc.ListStaticAddressWithdrawalResponse, error) {
1673+
1674+
withdrawals, err := s.withdrawalManager.GetAllWithdrawals(ctx)
1675+
if err != nil {
1676+
return nil, err
1677+
}
1678+
1679+
if len(withdrawals) == 0 {
1680+
return &looprpc.ListStaticAddressWithdrawalResponse{}, nil
1681+
}
1682+
1683+
clientWithdrawals := make(
1684+
[]*looprpc.StaticAddressWithdrawal, 0, len(withdrawals),
1685+
)
1686+
for _, w := range withdrawals {
1687+
withdrawal := &looprpc.StaticAddressWithdrawal{
1688+
TxId: w.TxID.String(),
1689+
Outpoints: w.DepositOutpoints,
1690+
TotalDepositAmountSatoshis: int64(w.TotalDepositAmount),
1691+
WithdrawnAmountSatoshis: int64(w.WithdrawnAmount),
1692+
ChangeAmountSatoshis: int64(w.ChangeAmount),
1693+
ConfirmationHeight: w.ConfirmationHeight,
1694+
}
1695+
clientWithdrawals = append(clientWithdrawals, withdrawal)
1696+
}
1697+
1698+
return &looprpc.ListStaticAddressWithdrawalResponse{
1699+
Withdrawals: clientWithdrawals,
1700+
}, nil
1701+
}
1702+
16681703
// ListStaticAddressSwaps returns a list of all swaps that are currently pending
16691704
// or previously succeeded.
16701705
func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,

staticaddr/withdraw/manager.go

+27-4
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ type ManagerConfig struct {
8282

8383
// Signer is the signer client that is used to sign transactions.
8484
Signer lndclient.SignerClient
85+
86+
// Store is the store that is used to persist the finalized withdrawal
87+
// transactions.
88+
Store *SqlStore
8589
}
8690

8791
// newWithdrawalRequest is used to send withdrawal request to the manager main
@@ -609,7 +613,8 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
609613

610614
go func() {
611615
select {
612-
case <-spentChan:
616+
case spentTx := <-spentChan:
617+
spendingHeight := uint32(spentTx.SpendingHeight)
613618
// If the transaction received one confirmation, we
614619
// ensure re-org safety by waiting for some more
615620
// confirmations.
@@ -621,7 +626,7 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
621626
int32(m.initiationHeight.Load()),
622627
)
623628
select {
624-
case <-confChan:
629+
case tx := <-confChan:
625630
err = m.cfg.DepositManager.TransitionDeposits(
626631
ctx, deposits, deposit.OnWithdrawn,
627632
deposit.Withdrawn,
@@ -631,12 +636,23 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
631636
"deposits: %v", err)
632637
}
633638

634-
// Remove the withdrawal tx from the active withdrawals
635-
// to stop republishing it on block arrivals.
639+
// Remove the withdrawal tx from the active
640+
// withdrawals to stop republishing it on block
641+
// arrivals.
636642
m.mu.Lock()
637643
delete(m.finalizedWithdrawalTxns, txHash)
638644
m.mu.Unlock()
639645

646+
// Persist info about the finalized withdrawal.
647+
err = m.cfg.Store.CreateWithdrawal(
648+
ctx, tx.Tx, spendingHeight, deposits,
649+
addrParams.PkScript,
650+
)
651+
if err != nil {
652+
log.Errorf("Error persisting "+
653+
"withdrawal: %v", err)
654+
}
655+
640656
case err := <-errChan:
641657
log.Errorf("Error waiting for confirmation: %v",
642658
err)
@@ -1116,3 +1132,10 @@ func (m *Manager) DeliverWithdrawalRequest(ctx context.Context,
11161132
"for withdrawal response")
11171133
}
11181134
}
1135+
1136+
// GetAllWithdrawals returns all finalized withdrawals from the store.
1137+
func (m *Manager) GetAllWithdrawals(ctx context.Context) ([]*Withdrawal,
1138+
error) {
1139+
1140+
return m.cfg.Store.AllWithdrawals(ctx)
1141+
}

staticaddr/withdraw/sql_store.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package withdraw
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"strings"
7+
8+
"github.com/btcsuite/btcd/btcutil"
9+
"github.com/btcsuite/btcd/chaincfg/chainhash"
10+
"github.com/btcsuite/btcd/wire"
11+
"github.com/lightninglabs/loop/loopdb"
12+
"github.com/lightninglabs/loop/loopdb/sqlc"
13+
"github.com/lightninglabs/loop/staticaddr/deposit"
14+
"github.com/lightningnetwork/lnd/clock"
15+
)
16+
17+
// SqlStore is the backing store for static address withdrawals.
18+
type SqlStore struct {
19+
baseDB *loopdb.BaseDB
20+
21+
clock clock.Clock
22+
}
23+
24+
// NewSqlStore constructs a new SQLStore from a BaseDB. The BaseDB is agnostic
25+
// to the underlying driver which can be postgres or sqlite.
26+
func NewSqlStore(db *loopdb.BaseDB) *SqlStore {
27+
return &SqlStore{
28+
baseDB: db,
29+
30+
clock: clock.NewDefaultClock(),
31+
}
32+
}
33+
34+
// CreateWithdrawal creates a static address withdrawal record in the database.
35+
func (s *SqlStore) CreateWithdrawal(ctx context.Context, tx *wire.MsgTx,
36+
confirmationHeight uint32, deposits []*deposit.Deposit,
37+
changePkScript []byte) error {
38+
39+
strOutpoints := make([]string, len(deposits))
40+
totalAmount := int64(0)
41+
for i, deposit := range deposits {
42+
strOutpoints[i] = deposit.OutPoint.String()
43+
totalAmount += int64(deposit.Value)
44+
}
45+
46+
// Populate the optional change amount.
47+
withdrawnAmount, changeAmount := int64(0), int64(0)
48+
if len(tx.TxOut) == 1 {
49+
withdrawnAmount = tx.TxOut[0].Value
50+
} else if len(tx.TxOut) == 2 {
51+
withdrawnAmount, changeAmount = tx.TxOut[0].Value, tx.TxOut[1].Value
52+
if bytes.Equal(changePkScript, tx.TxOut[0].PkScript) {
53+
changeAmount = tx.TxOut[0].Value
54+
withdrawnAmount = tx.TxOut[1].Value
55+
}
56+
}
57+
58+
createArgs := sqlc.CreateWithdrawalParams{
59+
WithdrawalTxID: tx.TxHash().String(),
60+
DepositOutpoints: strings.Join(strOutpoints, ","),
61+
TotalDepositAmount: totalAmount,
62+
WithdrawnAmount: withdrawnAmount,
63+
ChangeAmount: changeAmount,
64+
ConfirmationHeight: int64(confirmationHeight),
65+
}
66+
67+
return s.baseDB.ExecTx(ctx, &loopdb.SqliteTxOptions{},
68+
func(q *sqlc.Queries) error {
69+
return q.CreateWithdrawal(ctx, createArgs)
70+
})
71+
}
72+
73+
// AllWithdrawals retrieves all known withdrawals.
74+
func (s *SqlStore) AllWithdrawals(ctx context.Context) ([]*Withdrawal, error) {
75+
var allWithdrawals []*Withdrawal
76+
77+
err := s.baseDB.ExecTx(ctx, loopdb.NewSqlReadOpts(),
78+
func(q *sqlc.Queries) error {
79+
var err error
80+
81+
withdrawals, err := q.AllWithdrawals(ctx)
82+
if err != nil {
83+
return err
84+
}
85+
86+
for _, withdrawal := range withdrawals {
87+
w, err := s.toWithdrawal(withdrawal)
88+
if err != nil {
89+
return err
90+
}
91+
92+
allWithdrawals = append(allWithdrawals, w)
93+
}
94+
95+
return nil
96+
})
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
return allWithdrawals, nil
102+
}
103+
104+
// toDeposit converts an sql deposit to a deposit.
105+
func (s *SqlStore) toWithdrawal(row sqlc.Withdrawal) (*Withdrawal, error) {
106+
txHash, err := chainhash.NewHashFromStr(row.WithdrawalTxID)
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
return &Withdrawal{
112+
TxID: *txHash,
113+
DepositOutpoints: strings.Split(row.DepositOutpoints, ","),
114+
TotalDepositAmount: btcutil.Amount(row.TotalDepositAmount),
115+
WithdrawnAmount: btcutil.Amount(row.WithdrawnAmount),
116+
ChangeAmount: btcutil.Amount(row.ChangeAmount),
117+
ConfirmationHeight: uint32(row.ConfirmationHeight),
118+
}, nil
119+
}

staticaddr/withdraw/withdrawal.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package withdraw
2+
3+
import (
4+
"github.com/btcsuite/btcd/btcutil"
5+
"github.com/btcsuite/btcd/chaincfg/chainhash"
6+
)
7+
8+
// Withdrawal represents a finalized static address withdrawal record in the
9+
// database.
10+
type Withdrawal struct {
11+
// TxID is the transaction ID of the withdrawal.
12+
TxID chainhash.Hash
13+
14+
// DepositOutpoints is a list of outpoints that were used to fund the
15+
// withdrawal.
16+
DepositOutpoints []string
17+
18+
// TotalDepositAmount is the total amount of all deposits used to fund
19+
// the withdrawal.
20+
TotalDepositAmount btcutil.Amount
21+
22+
// WithdrawnAmount is the amount withdrawn. It represents the total
23+
// value of selected deposits minus fees and change.
24+
WithdrawnAmount btcutil.Amount
25+
26+
// ChangeAmount is the optional change returned to the static address.
27+
ChangeAmount btcutil.Amount
28+
29+
// ConfirmationHeight is the block height at which the withdrawal was
30+
// confirmed.
31+
ConfirmationHeight uint32
32+
}

0 commit comments

Comments
 (0)