Skip to content

Commit 00469c8

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 17ba66f commit 00469c8

File tree

7 files changed

+139
-0
lines changed

7 files changed

+139
-0
lines changed

assets/deposit/manager.go

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

176+
// Wake the manager up very 10 seconds to check if there're any pending
177+
// chores to do.
178+
const wakeupInterval = time.Duration(10) * time.Second
179+
withdrawTicker := time.NewTicker(wakeupInterval)
180+
176181
for {
177182
select {
178183
case <-m.callEnter:
@@ -192,6 +197,15 @@ func (m *Manager) Run(ctx context.Context, bestBlock uint32) error {
192197
return err
193198
}
194199

200+
case <-withdrawTicker.C:
201+
err := m.publishPendingWithdrawals(ctx)
202+
if err != nil {
203+
log.Errorf("Unable to publish pending "+
204+
"withdrawals: %v", err)
205+
206+
return err
207+
}
208+
195209
case err := <-blockErrChan:
196210
log.Errorf("received error from block epoch "+
197211
"notification: %v", err)
@@ -985,6 +999,8 @@ func (m *Manager) handleDepositSpend(ctx context.Context, d *Deposit,
985999

9861000
switch d.State {
9871001
case StateTimeoutSweepPublished:
1002+
fallthrough
1003+
case StateCooperativeSweepPublished:
9881004
d.State = StateSwept
9891005

9901006
err := m.releaseDepositSweepInputs(ctx, d)
@@ -1126,3 +1142,73 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
11261142

11271143
return nil
11281144
}
1145+
1146+
// publishPendingWithdrawals publishes any pending deposit withdrawals.
1147+
func (m *Manager) publishPendingWithdrawals(ctx context.Context) error {
1148+
for _, d := range m.deposits {
1149+
// TODO(bhandras): republish on StateCooperativeSweepPublished.
1150+
if d.State != StateWithdrawn {
1151+
continue
1152+
}
1153+
1154+
serverKey, err := m.store.GetAssetDepositServerKey(
1155+
ctx, d.ID,
1156+
)
1157+
if err != nil {
1158+
return err
1159+
}
1160+
1161+
lockID, err := d.lockID()
1162+
if err != nil {
1163+
return err
1164+
}
1165+
1166+
// TODO(bhandras): conf target should be dynamic/configrable.
1167+
const confTarget = 2
1168+
feeRateSatPerKw, err := m.walletKit.EstimateFeeRate(
1169+
ctx, confTarget,
1170+
)
1171+
if err != nil {
1172+
return err
1173+
}
1174+
1175+
funder := true
1176+
sendResp, err := m.sweeper.PublishDepositSweepMuSig2(
1177+
ctx, d.Kit, funder, d.Proof, serverKey,
1178+
asset.NewScriptKey(d.SweepScriptKey),
1179+
d.SweepInternalKey, d.withdrawLabel(),
1180+
feeRateSatPerKw.FeePerVByte(), lockID, lockExpiration,
1181+
)
1182+
if err != nil {
1183+
log.Errorf("Unable to publish deposit sweep for %v: %v",
1184+
d.ID, err)
1185+
} else {
1186+
log.Infof("Published sweep for deposit %v: %v", d.ID,
1187+
sendResp.Transfer.AnchorTxHash)
1188+
1189+
d.State = StateCooperativeSweepPublished
1190+
err = m.handleDepositStateUpdate(ctx, d)
1191+
if err != nil {
1192+
log.Errorf("Unable to update deposit %v "+
1193+
"state: %v", d.ID, err)
1194+
1195+
return err
1196+
}
1197+
}
1198+
1199+
// Start monitoring the sweep unless we're already doing so.
1200+
if _, ok := m.pendingSweeps[d.ID]; !ok {
1201+
err := m.waitForDepositSweep(ctx, d, d.withdrawLabel())
1202+
if err != nil {
1203+
log.Errorf("Unable to wait for deposit %v "+
1204+
"spend: %v", d.ID, err)
1205+
1206+
return err
1207+
}
1208+
1209+
m.pendingSweeps[d.ID] = struct{}{}
1210+
}
1211+
}
1212+
1213+
return nil
1214+
}

assets/deposit/manager_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ func (s *mockStore) SetAssetDepositServerKey(context.Context, string,
5454
return nil
5555
}
5656

57+
// GetAssetDepositServerKey is a mock implementation of the
58+
// GetAssetDepositServerKey method.
59+
func (s *mockStore) GetAssetDepositServerKey(context.Context, string) (
60+
*btcec.PrivateKey, error) {
61+
62+
// Return a dummy private key for testing
63+
privKey, _ := btcec.NewPrivateKey()
64+
return privKey, nil
65+
}
66+
5767
// testAddDeposit is a helper function that (intrusively) adds a deposit to the
5868
// manager.
5969
func testAddDeposit(t *testing.T, m *Manager, d *Deposit) {

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
@@ -329,3 +332,19 @@ func (s *SQLStore) SetAssetDepositServerKey(ctx context.Context,
329332
},
330333
)
331334
}
335+
336+
func (s *SQLStore) GetAssetDepositServerKey(ctx context.Context,
337+
depositID string) (*btcec.PrivateKey, error) {
338+
339+
keyBytes, err := s.db.GetAssetDepositServerInternalKey(ctx, depositID)
340+
if err != nil {
341+
return nil, err
342+
}
343+
344+
key, _ := btcec.PrivKeyFromBytes(keyBytes)
345+
if err != nil {
346+
return nil, err
347+
}
348+
349+
return key, nil
350+
}

assets/deposit/store.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,9 @@ type Store interface {
2626
// asset deposit.
2727
SetAssetDepositServerKey(ctx context.Context, depositID string,
2828
key *btcec.PrivateKey) error
29+
30+
// GetAssetDepositServerKey gets the server's internal key for the given
31+
// asset deposit.
32+
GetAssetDepositServerKey(ctx context.Context, depositID string) (
33+
*btcec.PrivateKey, error)
2934
}

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)