Skip to content

Commit b4276e5

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

File tree

5 files changed

+436
-9
lines changed

5 files changed

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

0 commit comments

Comments
 (0)