Skip to content

Commit 8a32efd

Browse files
committed
assets+loopdb: implement asset deposit withdrawal
This commit implements the necessary plumbing to enable deposit withdrawal. Withdrawing reveals the server's internal key for the deposit, allowing the client to sweep it independently.
1 parent eec1853 commit 8a32efd

File tree

6 files changed

+130
-1
lines changed

6 files changed

+130
-1
lines changed

assets/deposit/manager.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,3 +940,79 @@ func (m *Manager) releaseDepositSweepInputs(ctx context.Context,
940940

941941
return nil
942942
}
943+
944+
// WithdrawDeposits withdraws the deposits with the given IDs. It will first ask
945+
// the server for the deposit keys, then initate the withdrawal by updating the
946+
// deposit state.
947+
func (m *Manager) WithdrawDeposits(ctx context.Context,
948+
depositIDs []string) error {
949+
950+
done, err := m.scheduleNextCall()
951+
if err != nil {
952+
return err
953+
}
954+
defer done()
955+
956+
for _, depositID := range depositIDs {
957+
d, ok := m.deposits[depositID]
958+
if !ok {
959+
return fmt.Errorf("deposit %v not found", depositID)
960+
}
961+
962+
if d.State != StateConfirmed {
963+
return fmt.Errorf("deposit %v is not withdrawable, "+
964+
"current state: %v", depositID, d.State)
965+
}
966+
967+
log.Infof("Initiating deposit withdrawal %v: %v",
968+
depositID, d.Amount)
969+
}
970+
971+
keys, err := m.depositServiceClient.WithdrawAssetDeposits(
972+
ctx, &swapserverrpc.WithdrawAssetDepositsServerReq{
973+
DepositIds: depositIDs,
974+
},
975+
)
976+
if err != nil {
977+
return fmt.Errorf("unable to request withdrawal: %w", err)
978+
}
979+
980+
for depositID, privKeyBytes := range keys.DepositKeys {
981+
d, ok := m.deposits[depositID]
982+
if !ok {
983+
log.Warnf("Skipping withdrawal of unknown deposit: %v",
984+
depositID)
985+
continue
986+
}
987+
988+
privKey, pubKey := btcec.PrivKeyFromBytes(privKeyBytes)
989+
if !d.CoSignerInternalKey.IsEqual(pubKey) {
990+
return fmt.Errorf("revealed co-signer internal key "+
991+
"does not match local key for %v", depositID)
992+
}
993+
994+
err := m.store.SetAssetDepositServerKey(ctx, depositID, privKey)
995+
if err != nil {
996+
return err
997+
}
998+
999+
rpcSweepAddr, err := m.tapClient.NewAddr(
1000+
ctx, &taprpc.NewAddrRequest{
1001+
AssetId: d.AssetID[:],
1002+
Amt: d.Amount,
1003+
},
1004+
)
1005+
if err != nil {
1006+
return err
1007+
}
1008+
1009+
d.State = StateWithdrawn
1010+
d.SweepAddr = rpcSweepAddr.Encoded
1011+
err = m.handleDepositStateUpdate(ctx, d)
1012+
if err != nil {
1013+
return err
1014+
}
1015+
}
1016+
1017+
return nil
1018+
}

assets/deposit/server.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,21 @@ func (s *Server) WithdrawAssetDeposits(ctx context.Context,
138138
in *looprpc.WithdrawAssetDepositsRequest) (
139139
*looprpc.WithdrawAssetDepositsResponse, error) {
140140

141-
return nil, status.Error(codes.Unimplemented, "unimplemented")
141+
if s.manager == nil {
142+
return nil, ErrAssetDepositsUnavailable
143+
}
144+
145+
if len(in.DepositIds) == 0 {
146+
return nil, status.Error(codes.InvalidArgument,
147+
"at least one deposit id must be provided")
148+
}
149+
150+
err := s.manager.WithdrawDeposits(ctx, in.DepositIds)
151+
if err != nil {
152+
return nil, status.Error(codes.Internal, err.Error())
153+
}
154+
155+
return &looprpc.WithdrawAssetDepositsResponse{}, nil
142156
}
143157

144158
// TestCoSignAssetDepositHTLC is the rpc endpoint for loop clients to test

assets/deposit/sql_store.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ type Querier interface {
3535

3636
GetActiveAssetDeposits(ctx context.Context) (
3737
[]sqlc.GetActiveAssetDepositsRow, error)
38+
39+
SetAssetDepositServerInternalKey(ctx context.Context,
40+
arg sqlc.SetAssetDepositServerInternalKeyParams) error
3841
}
3942

4043
// DepositBaseDB is the interface that contains all the queries generated
@@ -298,3 +301,16 @@ func (s *SQLStore) GetActiveDeposits(ctx context.Context) ([]Deposit, error) {
298301

299302
return deposits, nil
300303
}
304+
305+
// SetAssetDepositServerKey sets the server's internal key for the give asset
306+
// deposit.
307+
func (s *SQLStore) SetAssetDepositServerKey(ctx context.Context,
308+
depositID string, key *btcec.PrivateKey) error {
309+
310+
return s.db.SetAssetDepositServerInternalKey(
311+
ctx, sqlc.SetAssetDepositServerInternalKeyParams{
312+
DepositID: depositID,
313+
ServerInternalKey: key.Serialize(),
314+
},
315+
)
316+
}

loopdb/sqlc/asset_deposits.sql.go

Lines changed: 17 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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,8 @@ WHERE u.id = (
5959
)
6060
AND u.update_state IN (0, 1, 2, 3, 4, 5, 6);
6161

62+
-- name: SetAssetDepositServerInternalKey :exec
63+
UPDATE asset_deposits
64+
SET server_internal_key = $2
65+
WHERE deposit_id = $1
66+
AND server_internal_key IS NULL;

0 commit comments

Comments
 (0)