Skip to content

Commit 2997d01

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

File tree

5 files changed

+438
-9
lines changed

5 files changed

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

0 commit comments

Comments
 (0)