Skip to content

Commit b954e59

Browse files
committed
assets+loopdb: implement functionality to list asset deposits
This commit adds the necessary changes to the asset deposit manager, the underlying sql store and the asset deposit subserver to be able to list asset deposits.
1 parent 685d50c commit b954e59

8 files changed

Lines changed: 326 additions & 1 deletion

File tree

assets/deposit/manager.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,3 +703,40 @@ func (m *Manager) markDepositConfirmed(ctx context.Context, d *Deposit,
703703

704704
return nil
705705
}
706+
707+
// ListDeposits returns all deposits that are in the given range of
708+
// confirmations.
709+
func (m *Manager) ListDeposits(ctx context.Context, minConfs, maxConfs uint32) (
710+
[]Deposit, error) {
711+
712+
bestBlock, err := m.GetBestBlock()
713+
if err != nil {
714+
return nil, err
715+
}
716+
717+
deposits, err := m.store.GetAllDeposits(ctx)
718+
if err != nil {
719+
return nil, err
720+
}
721+
722+
// Only filter based on confirmations if the user has set a min or max
723+
// confs.
724+
filterConfs := minConfs != 0 || maxConfs != 0
725+
726+
// Prefilter deposits based on the min/max confs.
727+
filteredDeposits := make([]Deposit, 0, len(deposits))
728+
for _, deposit := range deposits {
729+
if filterConfs {
730+
// Check that the deposit suits our min/max confs
731+
// criteria.
732+
confs := bestBlock - deposit.ConfirmationHeight
733+
if confs < minConfs || confs > maxConfs {
734+
continue
735+
}
736+
}
737+
738+
filteredDeposits = append(filteredDeposits, deposit)
739+
}
740+
741+
return filteredDeposits, nil
742+
}

assets/deposit/manager_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ func (s *mockStore) UpdateDeposit(context.Context, *Deposit) error {
3535
return nil
3636
}
3737

38+
// GetAllDeposits is a mock implementation of the GetAllDeposits method.
39+
func (s *mockStore) GetAllDeposits(context.Context) ([]Deposit, error) {
40+
return []Deposit{}, nil
41+
}
42+
3843
// testAddDeposit is a helper function that (intrusively) adds a deposit to the
3944
// manager.
4045
func testAddDeposit(t *testing.T, m *Manager, d *Deposit) {

assets/deposit/server.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,55 @@ func (s *Server) ListAssetDeposits(ctx context.Context,
8383
in *looprpc.ListAssetDepositsRequest) (
8484
*looprpc.ListAssetDepositsResponse, error) {
8585

86-
return nil, status.Error(codes.Unimplemented, "unimplemented")
86+
if s.manager == nil {
87+
return nil, ErrAssetDepositsUnavailable
88+
}
89+
90+
if in.MinConfs < in.MaxConfs {
91+
return nil, status.Error(codes.InvalidArgument,
92+
"max_confs must be greater than or equal to min_confs")
93+
}
94+
95+
deposits, err := s.manager.ListDeposits(ctx, in.MinConfs, in.MaxConfs)
96+
if err != nil {
97+
return nil, status.Error(codes.Internal, err.Error())
98+
}
99+
100+
filteredDeposits := make([]*looprpc.AssetDeposit, 0, len(deposits))
101+
for _, d := range deposits {
102+
rpcDeposit := &looprpc.AssetDeposit{
103+
DepositId: d.ID,
104+
CreatedAt: d.CreatedAt.Unix(),
105+
AssetId: d.AssetID.String(),
106+
Amount: d.Amount,
107+
DepositAddr: d.Addr,
108+
State: d.State.String(),
109+
ConfirmationHeight: d.ConfirmationHeight,
110+
Expiry: d.ConfirmationHeight + d.CsvExpiry,
111+
}
112+
113+
if d.Outpoint != nil {
114+
rpcDeposit.AnchorOutpoint = d.Outpoint.String()
115+
}
116+
117+
if d.SweepScriptKey != nil {
118+
rpcDeposit.SweepScriptKey = hex.EncodeToString(
119+
d.SweepScriptKey.SerializeCompressed(),
120+
)
121+
}
122+
123+
if d.SweepInternalKey != nil {
124+
rpcDeposit.SweepInternalKey = hex.EncodeToString(
125+
d.SweepInternalKey.SerializeCompressed(),
126+
)
127+
}
128+
129+
filteredDeposits = append(filteredDeposits, rpcDeposit)
130+
}
131+
132+
return &looprpc.ListAssetDepositsResponse{
133+
FilteredDeposits: filteredDeposits,
134+
}, nil
87135
}
88136

89137
// RevealAssetDepositKey is the rpc endpoint for loop clients to reveal the

assets/deposit/sql_store.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ package deposit
33
import (
44
"context"
55
"database/sql"
6+
"fmt"
67

8+
"github.com/btcsuite/btcd/btcec/v2"
79
"github.com/btcsuite/btcd/chaincfg"
10+
"github.com/btcsuite/btcd/wire"
811
"github.com/lightninglabs/loop/loopdb"
912
"github.com/lightninglabs/loop/loopdb/sqlc"
1013
"github.com/lightninglabs/taproot-assets/address"
14+
"github.com/lightninglabs/taproot-assets/asset"
1115
"github.com/lightningnetwork/lnd/clock"
16+
"github.com/lightningnetwork/lnd/keychain"
1217
)
1318

1419
// Querier is a subset of the methods we need from the postgres.querier
@@ -21,6 +26,9 @@ type Querier interface {
2126

2227
MarkDepositConfirmed(ctx context.Context,
2328
arg sqlc.MarkDepositConfirmedParams) error
29+
30+
GetAssetDeposits(ctx context.Context) ([]sqlc.GetAssetDepositsRow,
31+
error)
2432
}
2533

2634
// DepositBaseDB is the interface that contains all the queries generated
@@ -130,3 +138,132 @@ func (s *SQLStore) UpdateDeposit(ctx context.Context, d *Deposit) error {
130138
)
131139
})
132140
}
141+
142+
// GetAllDeposits returns all deposits known to the store.
143+
func (s *SQLStore) GetAllDeposits(ctx context.Context) ([]Deposit, error) {
144+
sqlDeposits, err := s.db.GetAssetDeposits(ctx)
145+
if err != nil {
146+
return nil, err
147+
}
148+
149+
deposits := make([]Deposit, 0, len(sqlDeposits))
150+
for _, sqlDeposit := range sqlDeposits {
151+
deposit, err := sqlcDepositToDeposit(
152+
sqlDeposit, &s.addressParams,
153+
)
154+
if err != nil {
155+
return nil, err
156+
}
157+
158+
deposits = append(deposits, deposit)
159+
}
160+
161+
return deposits, nil
162+
}
163+
164+
func sqlcDepositToDeposit(sqlDeposit sqlc.GetAssetDepositsRow,
165+
addressParams *address.ChainParams) (Deposit, error) {
166+
167+
clientScriptPubKey, err := btcec.ParsePubKey(
168+
sqlDeposit.ClientScriptPubkey,
169+
)
170+
if err != nil {
171+
return Deposit{}, err
172+
}
173+
174+
serverScriptPubKey, err := btcec.ParsePubKey(
175+
sqlDeposit.ServerScriptPubkey,
176+
)
177+
if err != nil {
178+
return Deposit{}, err
179+
}
180+
181+
clientInteralPubKey, err := btcec.ParsePubKey(
182+
sqlDeposit.ClientInternalPubkey,
183+
)
184+
if err != nil {
185+
return Deposit{}, err
186+
}
187+
188+
serverInternalPubKey, err := btcec.ParsePubKey(
189+
sqlDeposit.ServerInternalPubkey,
190+
)
191+
if err != nil {
192+
return Deposit{}, err
193+
}
194+
195+
clientKeyLocator := keychain.KeyLocator{
196+
Family: keychain.KeyFamily(
197+
sqlDeposit.ClientKeyFamily,
198+
),
199+
Index: uint32(sqlDeposit.ClientKeyIndex),
200+
}
201+
202+
if len(sqlDeposit.AssetID) != len(asset.ID{}) {
203+
return Deposit{}, fmt.Errorf("malformed asset ID for deposit: "+
204+
"%v", sqlDeposit.DepositID)
205+
}
206+
207+
depositInfo := &Info{
208+
ID: sqlDeposit.DepositID,
209+
Version: AssetDepositProtocolVersion(
210+
sqlDeposit.ProtocolVersion,
211+
),
212+
CreatedAt: sqlDeposit.CreatedAt.Local(), //nolint:gosmopolitan
213+
Amount: uint64(sqlDeposit.Amount),
214+
Addr: sqlDeposit.Addr,
215+
State: State(sqlDeposit.UpdateState),
216+
}
217+
218+
if sqlDeposit.ConfirmationHeight.Valid {
219+
depositInfo.ConfirmationHeight = uint32(
220+
sqlDeposit.ConfirmationHeight.Int32,
221+
)
222+
}
223+
224+
if sqlDeposit.Outpoint.Valid {
225+
outpoint, err := wire.NewOutPointFromString(
226+
sqlDeposit.Outpoint.String,
227+
)
228+
if err != nil {
229+
return Deposit{}, err
230+
}
231+
232+
depositInfo.Outpoint = outpoint
233+
}
234+
235+
if len(sqlDeposit.SweepInternalPubkey) > 0 {
236+
sweepInternalPubKey, err := btcec.ParsePubKey(
237+
sqlDeposit.SweepInternalPubkey,
238+
)
239+
if err != nil {
240+
return Deposit{}, err
241+
}
242+
depositInfo.SweepInternalKey = sweepInternalPubKey
243+
}
244+
245+
if len(sqlDeposit.SweepScriptPubkey) > 0 {
246+
sweepScriptPubKey, err := btcec.ParsePubKey(
247+
sqlDeposit.SweepScriptPubkey,
248+
)
249+
if err != nil {
250+
return Deposit{}, err
251+
}
252+
depositInfo.SweepScriptKey = sweepScriptPubKey
253+
}
254+
255+
kit, err := NewKit(
256+
clientScriptPubKey, clientInteralPubKey, serverScriptPubKey,
257+
serverInternalPubKey, clientKeyLocator,
258+
asset.ID(sqlDeposit.AssetID), uint32(sqlDeposit.Expiry),
259+
addressParams,
260+
)
261+
if err != nil {
262+
return Deposit{}, err
263+
}
264+
265+
return Deposit{
266+
Kit: kit,
267+
Info: depositInfo,
268+
}, nil
269+
}

assets/deposit/store.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ type Store interface {
1010
// UpdateDeposit updates the deposit state and extends the deposit
1111
// update log in the SQL store.
1212
UpdateDeposit(ctx context.Context, d *Deposit) error
13+
14+
// GetAllDeposits returns all deposits known to the store.
15+
GetAllDeposits(ctx context.Context) ([]Deposit, error)
1316
}

loopdb/sqlc/asset_deposits.sql.go

Lines changed: 82 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

loopdb/sqlc/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

loopdb/sqlc/queries/asset_deposits.sql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,15 @@ INSERT INTO asset_deposit_updates (
2727
UPDATE asset_deposits
2828
SET confirmation_height = $2, outpoint = $3, pk_script = $4
2929
WHERE deposit_id = $1;
30+
31+
-- name: GetAssetDeposits :many
32+
SELECT d.*, u.update_state, u.update_timestamp
33+
FROM asset_deposits d
34+
JOIN asset_deposit_updates u ON u.id = (
35+
SELECT id
36+
FROM asset_deposit_updates
37+
WHERE deposit_id = d.deposit_id
38+
ORDER BY update_timestamp DESC
39+
LIMIT 1
40+
)
41+
ORDER BY d.created_at ASC;

0 commit comments

Comments
 (0)