Skip to content

Commit 5b8ecd8

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 f562a1e commit 5b8ecd8

File tree

6 files changed

+129
-1
lines changed

6 files changed

+129
-1
lines changed

assets/deposit/manager.go

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

10111011
return nil
10121012
}
1013+
1014+
// WithdrawDeposits withdraws the deposits with the given IDs. It will first ask
1015+
// the server for the deposit keys, then initate the withdrawal by updating the
1016+
// deposit state.
1017+
func (m *Manager) WithdrawDeposits(ctx context.Context,
1018+
depositIDs []string) error {
1019+
1020+
done, err := m.scheduleNextCall()
1021+
if err != nil {
1022+
return err
1023+
}
1024+
defer done()
1025+
1026+
for _, depositID := range depositIDs {
1027+
d, ok := m.deposits[depositID]
1028+
if !ok {
1029+
return fmt.Errorf("deposit %v not found", depositID)
1030+
}
1031+
1032+
if d.State != StateConfirmed {
1033+
return fmt.Errorf("deposit %v is not withdrawable, "+
1034+
"current state: %v", depositID, d.State)
1035+
}
1036+
1037+
log.Infof("Initiating deposit withdrawal %v: %v",
1038+
depositID, d.Amount)
1039+
}
1040+
1041+
keys, err := m.depositServiceClient.WithdrawAssetDeposits(
1042+
ctx, &swapserverrpc.WithdrawAssetDepositsServerReq{
1043+
DepositIds: depositIDs,
1044+
},
1045+
)
1046+
if err != nil {
1047+
return fmt.Errorf("unable to request withdrawal: %w", err)
1048+
}
1049+
1050+
for depositID, privKeyBytes := range keys.DepositKeys {
1051+
d, ok := m.deposits[depositID]
1052+
if !ok {
1053+
log.Warnf("Skipping withdrawal of unknown deposit: %v",
1054+
depositID)
1055+
continue
1056+
}
1057+
1058+
privKey, pubKey := btcec.PrivKeyFromBytes(privKeyBytes)
1059+
if !d.CoSignerInternalKey.IsEqual(pubKey) {
1060+
return fmt.Errorf("revealed co-signer internal key "+
1061+
"does not match local key for %v", depositID)
1062+
}
1063+
1064+
err := m.store.SetAssetDepositServerKey(ctx, depositID, privKey)
1065+
if err != nil {
1066+
return err
1067+
}
1068+
1069+
d.State = StateWithdrawn
1070+
err = d.GenerateSweepKeys(ctx, m.tapClient)
1071+
if err != nil {
1072+
log.Errorf("Unable to generate sweep keys for deposit "+
1073+
"withdrawal %v: %v", d.ID, err)
1074+
1075+
return err
1076+
}
1077+
1078+
err = m.handleDepositStateUpdate(ctx, d)
1079+
if err != nil {
1080+
return err
1081+
}
1082+
}
1083+
1084+
return nil
1085+
}

assets/deposit/server.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,5 +149,19 @@ func (s *Server) WithdrawAssetDeposits(ctx context.Context,
149149
in *looprpc.WithdrawAssetDepositsRequest) (
150150
*looprpc.WithdrawAssetDepositsResponse, error) {
151151

152-
return nil, status.Error(codes.Unimplemented, "unimplemented")
152+
if s.manager == nil {
153+
return nil, ErrAssetDepositsUnavailable
154+
}
155+
156+
if len(in.DepositIds) == 0 {
157+
return nil, status.Error(codes.InvalidArgument,
158+
"at least one deposit id must be provided")
159+
}
160+
161+
err := s.manager.WithdrawDeposits(ctx, in.DepositIds)
162+
if err != nil {
163+
return nil, status.Error(codes.Internal, err.Error())
164+
}
165+
166+
return &looprpc.WithdrawAssetDepositsResponse{}, nil
153167
}

assets/deposit/sql_store.go

Lines changed: 18 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
@@ -135,6 +138,8 @@ func (s *SQLStore) UpdateDeposit(ctx context.Context, d *Deposit) error {
135138
}
136139

137140
case StateExpired:
141+
fallthrough
142+
case StateWithdrawn:
138143
scriptKey := d.SweepScriptKey.SerializeCompressed()
139144
internalKey := d.SweepInternalKey.SerializeCompressed()
140145
err := tx.SetAssetDepositSweepKeys(
@@ -310,3 +315,16 @@ func (s *SQLStore) GetActiveDeposits(ctx context.Context) ([]Deposit, error) {
310315

311316
return deposits, nil
312317
}
318+
319+
// SetAssetDepositServerKey sets the server's internal key for the give asset
320+
// deposit.
321+
func (s *SQLStore) SetAssetDepositServerKey(ctx context.Context,
322+
depositID string, key *btcec.PrivateKey) error {
323+
324+
return s.db.SetAssetDepositServerInternalKey(
325+
ctx, sqlc.SetAssetDepositServerInternalKeyParams{
326+
DepositID: depositID,
327+
ServerInternalKey: key.Serialize(),
328+
},
329+
)
330+
}

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)