Skip to content

Commit 9e71632

Browse files
committed
assets+loopdb: publish cooperative deposit withdrawal transaction
This commit implements the full cooperative deposit withdrawal flow. The client first fetches keys for any pending withdrawals, then publishes sweep transactions using the revealed key to sign the deposit sweep. Once the sweep confirms, the deposit’s state is updated in the deposit store.
1 parent bea1827 commit 9e71632

File tree

5 files changed

+124
-0
lines changed

5 files changed

+124
-0
lines changed

assets/deposit/manager.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@ func (m *Manager) Run(ctx context.Context, bestBlock uint32) error {
169169
return err
170170
}
171171

172+
// Wake the manager up very 10 seconds to check if there're any pending
173+
// chores to do.
174+
const wakeupInterval = time.Duration(10) * time.Second
175+
withdrawTicker := time.NewTicker(wakeupInterval)
176+
172177
for {
173178
select {
174179
case <-m.callEnter:
@@ -188,6 +193,15 @@ func (m *Manager) Run(ctx context.Context, bestBlock uint32) error {
188193
return err
189194
}
190195

196+
case <-withdrawTicker.C:
197+
err := m.publishPendingWithdrawals(ctx)
198+
if err != nil {
199+
log.Errorf("Unable to publish pending "+
200+
"withdrawals: %v", err)
201+
202+
return err
203+
}
204+
191205
case err := <-blockErrChan:
192206
log.Errorf("received error from block epoch "+
193207
"notification: %v", err)
@@ -944,6 +958,8 @@ func (m *Manager) handleDepositSpend(ctx context.Context, d *Deposit,
944958

945959
switch d.State {
946960
case StateTimeoutSweepPublished:
961+
fallthrough
962+
case StateCooperativeSweepPublished:
947963
d.State = StateSwept
948964

949965
err := m.releaseDepositSweepInputs(ctx, d)
@@ -1085,3 +1101,73 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
10851101

10861102
return nil
10871103
}
1104+
1105+
// publishPendingWithdrawals publishes any pending deposit withdrawals.
1106+
func (m *Manager) publishPendingWithdrawals(ctx context.Context) error {
1107+
for _, d := range m.deposits {
1108+
// TODO(bhandras): republish on StateCooperativeSweepPublished.
1109+
if d.State != StateWithdrawn {
1110+
continue
1111+
}
1112+
1113+
serverKey, err := m.store.GetAssetDepositServerKey(
1114+
ctx, d.ID,
1115+
)
1116+
if err != nil {
1117+
return err
1118+
}
1119+
1120+
lockID, err := d.lockID()
1121+
if err != nil {
1122+
return err
1123+
}
1124+
1125+
// TODO(bhandras): conf target should be dynamic/configrable.
1126+
const confTarget = 2
1127+
feeRateSatPerKw, err := m.walletKit.EstimateFeeRate(
1128+
ctx, confTarget,
1129+
)
1130+
if err != nil {
1131+
return err
1132+
}
1133+
1134+
funder := true
1135+
sendResp, err := m.sweeper.PublishDepositSweepMuSig2(
1136+
ctx, d.Kit, funder, d.Proof, serverKey,
1137+
asset.NewScriptKey(d.SweepScriptKey),
1138+
d.SweepInternalKey, d.withdrawLabel(),
1139+
feeRateSatPerKw.FeePerVByte(), lockID, lockExpiration,
1140+
)
1141+
if err != nil {
1142+
log.Errorf("Unable to publish deposit sweep for %v: %v",
1143+
d.ID, err)
1144+
} else {
1145+
log.Infof("Published sweep for deposit %v: %v", d.ID,
1146+
sendResp.Transfer.AnchorTxHash)
1147+
1148+
d.State = StateCooperativeSweepPublished
1149+
err = m.handleDepositStateUpdate(ctx, d)
1150+
if err != nil {
1151+
log.Errorf("Unable to update deposit %v "+
1152+
"state: %v", d.ID, err)
1153+
1154+
return err
1155+
}
1156+
}
1157+
1158+
// Start monitoring the sweep unless we're already doing so.
1159+
if _, ok := m.pendingSweeps[d.ID]; !ok {
1160+
err := m.waitForDepositSweep(ctx, d, d.withdrawLabel())
1161+
if err != nil {
1162+
log.Errorf("Unable to wait for deposit %v "+
1163+
"spend: %v", d.ID, err)
1164+
1165+
return err
1166+
}
1167+
1168+
m.pendingSweeps[d.ID] = struct{}{}
1169+
}
1170+
}
1171+
1172+
return nil
1173+
}

assets/deposit/sql_store.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ type Querier interface {
3838

3939
SetAssetDepositServerInternalKey(ctx context.Context,
4040
arg sqlc.SetAssetDepositServerInternalKeyParams) error
41+
42+
GetAssetDepositServerInternalKey(ctx context.Context,
43+
depositID string) ([]byte, error)
4144
}
4245

4346
// DepositBaseDB is the interface that contains all the queries generated
@@ -328,3 +331,19 @@ func (s *SQLStore) SetAssetDepositServerKey(ctx context.Context,
328331
},
329332
)
330333
}
334+
335+
func (s *SQLStore) GetAssetDepositServerKey(ctx context.Context,
336+
depositID string) (*btcec.PrivateKey, error) {
337+
338+
keyBytes, err := s.db.GetAssetDepositServerInternalKey(ctx, depositID)
339+
if err != nil {
340+
return nil, err
341+
}
342+
343+
key, _ := btcec.PrivKeyFromBytes(keyBytes)
344+
if err != nil {
345+
return nil, err
346+
}
347+
348+
return key, nil
349+
}

loopdb/sqlc/asset_deposits.sql.go

Lines changed: 13 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
@@ -64,3 +64,8 @@ UPDATE asset_deposits
6464
SET server_internal_key = $2
6565
WHERE deposit_id = $1
6666
AND server_internal_key IS NULL;
67+
68+
-- name: GetAssetDepositServerInternalKey :one
69+
SELECT server_internal_key
70+
FROM asset_deposits
71+
WHERE deposit_id = $1;

0 commit comments

Comments
 (0)