Skip to content

Commit 05a15ad

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

File tree

4 files changed

+337
-14
lines changed

4 files changed

+337
-14
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: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"reflect"
8-
"strings"
9-
"sync"
10-
"sync/atomic"
11-
127
"github.com/btcsuite/btcd/btcec/v2/schnorr"
138
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
149
"github.com/btcsuite/btcd/btcutil"
@@ -26,6 +21,10 @@ import (
2621
"github.com/lightningnetwork/lnd/lntypes"
2722
"github.com/lightningnetwork/lnd/lnwallet"
2823
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
24+
"reflect"
25+
"strings"
26+
"sync"
27+
"sync/atomic"
2928
)
3029

3130
var (
@@ -82,6 +81,10 @@ type ManagerConfig struct {
8281

8382
// Signer is the signer client that is used to sign transactions.
8483
Signer lndclient.SignerClient
84+
85+
// Store is the store that is used to persist the finalized withdrawal
86+
// transactions.
87+
Store *SqlStore
8588
}
8689

8790
// newWithdrawalRequest is used to send withdrawal request to the manager main
@@ -401,6 +404,13 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
401404
// republished in case of a fee bump, it suffices if only one spent
402405
// notifier is run.
403406
if allDeposited {
407+
// Persist info about the finalized withdrawal.
408+
err = m.cfg.Store.CreateWithdrawal(ctx, deposits)
409+
if err != nil {
410+
log.Errorf("Error persisting "+
411+
"withdrawal: %v", err)
412+
}
413+
404414
err = m.handleWithdrawal(
405415
ctx, deposits, finalizedTx.TxHash(), withdrawalPkScript,
406416
)
@@ -592,9 +602,7 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
592602
deposits []*deposit.Deposit, txHash chainhash.Hash,
593603
withdrawalPkscript []byte) error {
594604

595-
addrParams, err := m.cfg.AddressManager.GetStaticAddressParameters(
596-
ctx,
597-
)
605+
addrParams, err := m.cfg.AddressManager.GetStaticAddressParameters(ctx)
598606
if err != nil {
599607
log.Errorf("error retrieving address params %w", err)
600608

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

610618
go func() {
611619
select {
612-
case <-spentChan:
620+
case spentTx := <-spentChan:
621+
spendingHeight := uint32(spentTx.SpendingHeight)
613622
// If the transaction received one confirmation, we
614623
// ensure re-org safety by waiting for some more
615624
// confirmations.
616625
var confChan chan *chainntnfs.TxConfirmation
617626
confChan, errChan, err =
618627
m.cfg.ChainNotifier.RegisterConfirmationsNtfn(
619-
ctx, &txHash, withdrawalPkscript,
620-
MinConfs,
628+
ctx, spentTx.SpenderTxHash,
629+
withdrawalPkscript, MinConfs,
621630
int32(m.initiationHeight.Load()),
622631
)
623632
select {
624-
case <-confChan:
633+
case tx := <-confChan:
625634
err = m.cfg.DepositManager.TransitionDeposits(
626635
ctx, deposits, deposit.OnWithdrawn,
627636
deposit.Withdrawn,
@@ -631,12 +640,23 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
631640
"deposits: %v", err)
632641
}
633642

634-
// Remove the withdrawal tx from the active withdrawals
635-
// to stop republishing it on block arrivals.
643+
// Remove the withdrawal tx from the active
644+
// withdrawals to stop republishing it on block
645+
// arrivals.
636646
m.mu.Lock()
637647
delete(m.finalizedWithdrawalTxns, txHash)
638648
m.mu.Unlock()
639649

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

staticaddr/withdraw/sql_store.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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+
// UpdateWithdrawala
115+
func (s *SqlStore) UpdateWithdrawal(ctx context.Context,
116+
deposits []*deposit.Deposit, tx *wire.MsgTx, confirmationHeight uint32,
117+
changePkScript []byte) error {
118+
119+
// Populate the optional change amount.
120+
withdrawnAmount, changeAmount := int64(0), int64(0)
121+
if len(tx.TxOut) == 1 {
122+
withdrawnAmount = tx.TxOut[0].Value
123+
} else if len(tx.TxOut) == 2 {
124+
withdrawnAmount, changeAmount = tx.TxOut[0].Value, tx.TxOut[1].Value
125+
if bytes.Equal(changePkScript, tx.TxOut[0].PkScript) {
126+
changeAmount = tx.TxOut[0].Value
127+
withdrawnAmount = tx.TxOut[1].Value
128+
}
129+
}
130+
131+
updateArgs := sqlc.UpdateWithdrawalParams{
132+
WithdrawalTxID: sql.NullString{
133+
String: tx.TxHash().String(),
134+
Valid: true,
135+
},
136+
WithdrawnAmount: sql.NullInt64{
137+
Int64: withdrawnAmount,
138+
Valid: withdrawnAmount > 0,
139+
},
140+
ChangeAmount: sql.NullInt64{
141+
Int64: changeAmount,
142+
Valid: changeAmount > 0,
143+
},
144+
ConfirmationHeight: sql.NullInt64{
145+
Int64: int64(confirmationHeight),
146+
Valid: confirmationHeight > 0,
147+
},
148+
}
149+
150+
return s.baseDB.ExecTx(ctx, &loopdb.SqliteTxOptions{},
151+
func(q Querier) error {
152+
withdrawalID, err := q.GetWithdrawalIDByDepositID(
153+
ctx, deposits[0].ID[:],
154+
)
155+
if err != nil {
156+
return err
157+
}
158+
159+
updateArgs.WithdrawalID = withdrawalID
160+
err = q.UpdateWithdrawal(ctx, updateArgs)
161+
if err != nil {
162+
return err
163+
}
164+
165+
return nil
166+
})
167+
}
168+
169+
// GetAllWithdrawals retrieves all static address withdrawals from the
170+
// database. It returns a slice of Withdrawal structs, each containing a list
171+
// of associated deposits.
172+
func (s *SqlStore) GetAllWithdrawals(ctx context.Context) ([]Withdrawal,
173+
error) {
174+
175+
withdrawals, err := s.baseDB.GetAllWithdrawals(ctx)
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
result := make([]Withdrawal, 0, len(withdrawals))
181+
for _, w := range withdrawals {
182+
depositIDs, err := s.baseDB.GetWithdrawalDeposits(ctx,
183+
w.WithdrawalID)
184+
185+
if err != nil {
186+
return nil, err
187+
}
188+
189+
deposits := make([]*deposit.Deposit, 0, len(depositIDs))
190+
for _, dID := range depositIDs {
191+
deposit, err := s.depositStore.GetDeposit(
192+
ctx, deposit.ID(dID),
193+
)
194+
if err != nil {
195+
return nil, err
196+
}
197+
deposits = append(deposits, deposit)
198+
}
199+
200+
txID, err := chainhash.NewHashFromStr(w.WithdrawalTxID.String)
201+
if err != nil {
202+
return nil, err
203+
}
204+
205+
result = append(result, Withdrawal{
206+
ID: ID(w.WithdrawalID),
207+
TxID: *txID,
208+
Deposits: deposits,
209+
TotalDepositAmount: btcutil.Amount(w.TotalDepositAmount),
210+
WithdrawnAmount: btcutil.Amount(w.WithdrawnAmount.Int64),
211+
ChangeAmount: btcutil.Amount(w.ChangeAmount.Int64),
212+
InitiationTime: w.InitiationTime,
213+
ConfirmationHeight: w.ConfirmationHeight.Int64,
214+
})
215+
}
216+
217+
return result, nil
218+
}

0 commit comments

Comments
 (0)