Skip to content

Commit 5718f11

Browse files
committed
staticaddr: store historic withdrawal info
1 parent cd9bcdf commit 5718f11

File tree

5 files changed

+437
-9
lines changed

5 files changed

+437
-9
lines changed

staticaddr/withdraw/interface.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ import (
1111
"github.com/lightningnetwork/lnd/lnwallet"
1212
)
1313

14+
// Store is the database interface that is used to store and retrieve
15+
// static address withdrawals.
16+
type Store interface {
17+
// CreateWithdrawal inserts a withdrawal into the store.
18+
CreateWithdrawal(ctx context.Context, tx *wire.MsgTx,
19+
confirmationHeight uint32, deposits []*deposit.Deposit,
20+
changePkScript []byte) error
21+
22+
// GetAllWithdrawals retrieves all withdrawals.
23+
GetAllWithdrawals(ctx context.Context) ([]Withdrawal, error)
24+
}
25+
1426
// AddressManager handles fetching of address parameters.
1527
type AddressManager interface {
1628
// GetStaticAddressParameters returns the static address parameters.

staticaddr/withdraw/manager.go

Lines changed: 35 additions & 9 deletions
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
@@ -401,6 +405,13 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
401405
// republished in case of a fee bump, it suffices if only one spent
402406
// notifier is run.
403407
if allDeposited {
408+
// Persist info about the finalized withdrawal.
409+
err = m.cfg.Store.CreateWithdrawal(ctx, deposits)
410+
if err != nil {
411+
log.Errorf("Error persisting "+
412+
"withdrawal: %v", err)
413+
}
414+
404415
err = m.handleWithdrawal(
405416
ctx, deposits, finalizedTx.TxHash(), withdrawalPkScript,
406417
)
@@ -592,9 +603,7 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
592603
deposits []*deposit.Deposit, txHash chainhash.Hash,
593604
withdrawalPkscript []byte) error {
594605

595-
addrParams, err := m.cfg.AddressManager.GetStaticAddressParameters(
596-
ctx,
597-
)
606+
addrParams, err := m.cfg.AddressManager.GetStaticAddressParameters(ctx)
598607
if err != nil {
599608
log.Errorf("error retrieving address params %w", err)
600609

@@ -609,19 +618,20 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
609618

610619
go func() {
611620
select {
612-
case <-spentChan:
621+
case spentTx := <-spentChan:
622+
spendingHeight := uint32(spentTx.SpendingHeight)
613623
// If the transaction received one confirmation, we
614624
// ensure re-org safety by waiting for some more
615625
// confirmations.
616626
var confChan chan *chainntnfs.TxConfirmation
617627
confChan, errChan, err =
618628
m.cfg.ChainNotifier.RegisterConfirmationsNtfn(
619-
ctx, &txHash, withdrawalPkscript,
620-
MinConfs,
629+
ctx, spentTx.SpenderTxHash,
630+
withdrawalPkscript, MinConfs,
621631
int32(m.initiationHeight.Load()),
622632
)
623633
select {
624-
case <-confChan:
634+
case tx := <-confChan:
625635
err = m.cfg.DepositManager.TransitionDeposits(
626636
ctx, deposits, deposit.OnWithdrawn,
627637
deposit.Withdrawn,
@@ -631,12 +641,23 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
631641
"deposits: %v", err)
632642
}
633643

634-
// Remove the withdrawal tx from the active withdrawals
635-
// to stop republishing it on block arrivals.
644+
// Remove the withdrawal tx from the active
645+
// withdrawals to stop republishing it on block
646+
// arrivals.
636647
m.mu.Lock()
637648
delete(m.finalizedWithdrawalTxns, txHash)
638649
m.mu.Unlock()
639650

651+
// Persist info about the finalized withdrawal.
652+
err = m.cfg.Store.UpdateWithdrawal(
653+
ctx, deposits, tx.Tx, spendingHeight,
654+
addrParams.PkScript,
655+
)
656+
if err != nil {
657+
log.Errorf("Error persisting "+
658+
"withdrawal: %v", err)
659+
}
660+
640661
case err := <-errChan:
641662
log.Errorf("Error waiting for confirmation: %v",
642663
err)
@@ -1116,3 +1137,8 @@ func (m *Manager) DeliverWithdrawalRequest(ctx context.Context,
11161137
"for withdrawal response")
11171138
}
11181139
}
1140+
1141+
// GetAllWithdrawals returns all finalized withdrawals from the store.
1142+
func (m *Manager) GetAllWithdrawals(ctx context.Context) ([]Withdrawal, error) {
1143+
return m.cfg.Store.GetAllWithdrawals(ctx)
1144+
}

staticaddr/withdraw/sql_store.go

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
package withdraw
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"database/sql"
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+
type Querier interface {
18+
// CreateWithdrawal inserts a new withdrawal.
19+
CreateWithdrawal(ctx context.Context,
20+
arg sqlc.CreateWithdrawalParams) error
21+
22+
// UpdateWithdrawal updates a withdrawal with confirmation parameters.
23+
UpdateWithdrawal(ctx context.Context,
24+
arg sqlc.UpdateWithdrawalParams) error
25+
26+
// GetWithdrawalIDByDepositID retrieves the withdrawal ID associated
27+
// with a given deposit ID.
28+
GetWithdrawalIDByDepositID(ctx context.Context, depositID []byte) (
29+
[]byte, error)
30+
31+
// CreateWithdrawalDeposit links withdrawal to deposits.
32+
CreateWithdrawalDeposit(ctx context.Context,
33+
arg sqlc.CreateWithdrawalDepositParams) error
34+
35+
// GetWithdrawalDeposits retrieves the deposit IDs associated with a
36+
// withdrawal.
37+
GetWithdrawalDeposits(ctx context.Context, withdrawalID []byte) (
38+
[][]byte, error)
39+
40+
// GetAllWithdrawals retrieves all withdrawals from the database.
41+
GetAllWithdrawals(ctx context.Context) ([]sqlc.Withdrawal, error)
42+
}
43+
44+
// BaseDB is the interface that contains all the queries generated by sqlc for
45+
// the static_address_swaps table and transaction functionality.
46+
type BaseDB interface {
47+
Querier
48+
49+
// ExecTx allows for executing a function in the context of a database
50+
// transaction.
51+
ExecTx(ctx context.Context, txOptions loopdb.TxOptions,
52+
txBody func(Querier) error) error
53+
}
54+
55+
// SqlStore is the backing store for static address withdrawals.
56+
type SqlStore struct {
57+
baseDB BaseDB
58+
depositStore deposit.Store
59+
clock clock.Clock
60+
}
61+
62+
// NewSqlStore constructs a new SQLStore from a BaseDB. The BaseDB is agnostic
63+
// to the underlying driver which can be postgres or sqlite.
64+
func NewSqlStore(db BaseDB, depositStore deposit.Store) *SqlStore {
65+
return &SqlStore{
66+
baseDB: db,
67+
depositStore: depositStore,
68+
clock: clock.NewDefaultClock(),
69+
}
70+
}
71+
72+
// CreateWithdrawal creates a static address withdrawal record in the database.
73+
func (s *SqlStore) CreateWithdrawal(ctx context.Context,
74+
deposits []*deposit.Deposit) error {
75+
76+
id, err := GetRandomWithdrawalID()
77+
if err != nil {
78+
return err
79+
}
80+
81+
var totalAmount btcutil.Amount
82+
for _, deposit := range deposits {
83+
totalAmount += deposit.Value
84+
}
85+
86+
createArgs := sqlc.CreateWithdrawalParams{
87+
WithdrawalID: id[:],
88+
TotalDepositAmount: int64(totalAmount),
89+
InitiationTime: s.clock.Now().UTC(),
90+
}
91+
return s.baseDB.ExecTx(ctx, &loopdb.SqliteTxOptions{},
92+
func(q Querier) error {
93+
err := q.CreateWithdrawal(ctx, createArgs)
94+
if err != nil {
95+
return err
96+
}
97+
98+
for _, deposit := range deposits {
99+
err = q.CreateWithdrawalDeposit(
100+
ctx, sqlc.CreateWithdrawalDepositParams{
101+
WithdrawalID: id[:],
102+
DepositID: deposit.ID[:],
103+
})
104+
if err != nil {
105+
return err
106+
}
107+
}
108+
109+
return nil
110+
})
111+
}
112+
113+
// UpdateWithdrawal updates a withdrawal record with the transaction
114+
// information, including the withdrawn amount, change amount, and
115+
// confirmation height. It is expected that the withdrawal has already been
116+
// created with CreateWithdrawal, and that the deposits slice contains the
117+
// deposits associated with the withdrawal.
118+
func (s *SqlStore) UpdateWithdrawal(ctx context.Context,
119+
deposits []*deposit.Deposit, tx *wire.MsgTx, confirmationHeight uint32,
120+
changePkScript []byte) error {
121+
122+
// Populate the optional change amount.
123+
withdrawnAmount, changeAmount := int64(0), int64(0)
124+
if len(tx.TxOut) == 1 {
125+
withdrawnAmount = tx.TxOut[0].Value
126+
} else if len(tx.TxOut) == 2 {
127+
withdrawnAmount, changeAmount = tx.TxOut[0].Value, tx.TxOut[1].Value
128+
if bytes.Equal(changePkScript, tx.TxOut[0].PkScript) {
129+
changeAmount = tx.TxOut[0].Value
130+
withdrawnAmount = tx.TxOut[1].Value
131+
}
132+
}
133+
134+
updateArgs := sqlc.UpdateWithdrawalParams{
135+
WithdrawalTxID: sql.NullString{
136+
String: tx.TxHash().String(),
137+
Valid: true,
138+
},
139+
WithdrawnAmount: sql.NullInt64{
140+
Int64: withdrawnAmount,
141+
Valid: withdrawnAmount > 0,
142+
},
143+
ChangeAmount: sql.NullInt64{
144+
Int64: changeAmount,
145+
Valid: changeAmount > 0,
146+
},
147+
ConfirmationHeight: sql.NullInt64{
148+
Int64: int64(confirmationHeight),
149+
Valid: confirmationHeight > 0,
150+
},
151+
}
152+
153+
return s.baseDB.ExecTx(ctx, &loopdb.SqliteTxOptions{},
154+
func(q Querier) error {
155+
withdrawalID, err := q.GetWithdrawalIDByDepositID(
156+
ctx, deposits[0].ID[:],
157+
)
158+
if err != nil {
159+
return err
160+
}
161+
162+
updateArgs.WithdrawalID = withdrawalID
163+
err = q.UpdateWithdrawal(ctx, updateArgs)
164+
if err != nil {
165+
return err
166+
}
167+
168+
return nil
169+
})
170+
}
171+
172+
// GetAllWithdrawals retrieves all static address withdrawals from the
173+
// database. It returns a slice of Withdrawal structs, each containing a list
174+
// of associated deposits.
175+
func (s *SqlStore) GetAllWithdrawals(ctx context.Context) ([]Withdrawal,
176+
error) {
177+
178+
withdrawals, err := s.baseDB.GetAllWithdrawals(ctx)
179+
if err != nil {
180+
return nil, err
181+
}
182+
183+
result := make([]Withdrawal, 0, len(withdrawals))
184+
for _, w := range withdrawals {
185+
depositIDs, err := s.baseDB.GetWithdrawalDeposits(ctx,
186+
w.WithdrawalID)
187+
188+
if err != nil {
189+
return nil, err
190+
}
191+
192+
deposits := make([]*deposit.Deposit, 0, len(depositIDs))
193+
for _, dID := range depositIDs {
194+
deposit, err := s.depositStore.GetDeposit(
195+
ctx, deposit.ID(dID),
196+
)
197+
if err != nil {
198+
return nil, err
199+
}
200+
deposits = append(deposits, deposit)
201+
}
202+
203+
txID, err := chainhash.NewHashFromStr(w.WithdrawalTxID.String)
204+
if err != nil {
205+
return nil, err
206+
}
207+
208+
result = append(result, Withdrawal{
209+
ID: ID(w.WithdrawalID),
210+
TxID: *txID,
211+
Deposits: deposits,
212+
TotalDepositAmount: btcutil.Amount(w.TotalDepositAmount),
213+
WithdrawnAmount: btcutil.Amount(w.WithdrawnAmount.Int64),
214+
ChangeAmount: btcutil.Amount(w.ChangeAmount.Int64),
215+
InitiationTime: w.InitiationTime,
216+
ConfirmationHeight: w.ConfirmationHeight.Int64,
217+
})
218+
}
219+
220+
return result, nil
221+
}

0 commit comments

Comments
 (0)