Skip to content

Commit da9455c

Browse files
committed
staticaddr: store historic withdrawal info
1 parent 85b56ed commit da9455c

File tree

4 files changed

+282
-4
lines changed

4 files changed

+282
-4
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: 25 additions & 4 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
@@ -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,8 @@ 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, error) {
1138+
return m.cfg.Store.GetAllWithdrawals(ctx)
1139+
}

staticaddr/withdraw/sql_store.go

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

staticaddr/withdraw/withdrawal.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package withdraw
2+
3+
import (
4+
"crypto/rand"
5+
"fmt"
6+
"time"
7+
8+
"github.com/btcsuite/btcd/btcutil"
9+
"github.com/btcsuite/btcd/chaincfg/chainhash"
10+
"github.com/lightninglabs/loop/staticaddr/deposit"
11+
)
12+
13+
const (
14+
IdLength = 32
15+
)
16+
17+
// ID is a unique identifier for a deposit.
18+
type ID [IdLength]byte
19+
20+
// FromByteSlice creates a deposit id from a byte slice.
21+
func (r *ID) FromByteSlice(b []byte) error {
22+
if len(b) != IdLength {
23+
return fmt.Errorf("withdrawal id must be 32 bytes, got %d, %x",
24+
len(b), b)
25+
}
26+
27+
copy(r[:], b)
28+
29+
return nil
30+
}
31+
32+
// Withdrawal represents a finalized static address withdrawal record in the
33+
// database.
34+
type Withdrawal struct {
35+
// ID is the unique identifier of the deposit.
36+
ID ID
37+
38+
// TxID is the transaction ID of the withdrawal.
39+
TxID chainhash.Hash
40+
41+
// Deposits is a list of deposits used to fund the withdrawal.
42+
Deposits []*deposit.Deposit
43+
44+
// TotalDepositAmount is the total amount of all deposits used to fund
45+
// the withdrawal.
46+
TotalDepositAmount btcutil.Amount
47+
48+
// WithdrawnAmount is the amount withdrawn. It represents the total
49+
// value of selected deposits minus fees and change.
50+
WithdrawnAmount btcutil.Amount
51+
52+
// ChangeAmount is the optional change returned to the static address.
53+
ChangeAmount btcutil.Amount
54+
55+
// InitiationTime is the time at which the withdrawal was initiated.
56+
InitiationTime time.Time
57+
58+
// ConfirmationHeight is the block height at which the withdrawal was
59+
// confirmed.
60+
ConfirmationHeight int64
61+
}
62+
63+
// GetRandomWithdrawalID generates a random withdrawal ID.
64+
func GetRandomWithdrawalID() (ID, error) {
65+
var id ID
66+
_, err := rand.Read(id[:])
67+
return id, err
68+
}

0 commit comments

Comments
 (0)