Skip to content

Commit 21cdcda

Browse files
committed
staticaddr: listen for spents on withdrawals
1 parent 99eb4f3 commit 21cdcda

File tree

2 files changed

+84
-26
lines changed

2 files changed

+84
-26
lines changed

staticaddr/deposit/fsm.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,8 @@ func (f *FSM) DepositStatesV0() fsm.States {
361361
},
362362
Withdrawn: fsm.State{
363363
Transitions: fsm.Transitions{
364-
OnExpiry: Expired,
364+
OnExpiry: Expired,
365+
OnWithdrawn: Withdrawn,
365366
},
366367
Action: f.FinalizeDepositAction,
367368
},

staticaddr/withdraw/manager.go

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"reflect"
88
"strings"
9+
"sync"
910
"sync/atomic"
1011

1112
"github.com/btcsuite/btcd/btcec/v2/schnorr"
@@ -93,6 +94,9 @@ type newWithdrawalResponse struct {
9394
type Manager struct {
9495
cfg *ManagerConfig
9596

97+
// mu protects access to finalizedWithdrawalTxns.
98+
mu sync.Mutex
99+
96100
// initChan signals the daemon that the withdrawal manager has completed
97101
// its initialization.
98102
initChan chan struct{}
@@ -243,14 +247,14 @@ func (m *Manager) recoverWithdrawals(ctx context.Context) error {
243247
return err
244248
}
245249

246-
err = m.handleWithdrawal(
247-
ctx, deposits, tx.TxHash(), tx.TxOut[0].PkScript,
248-
)
250+
err = m.handleWithdrawal(ctx, deposits, tx.TxHash())
249251
if err != nil {
250252
return err
251253
}
252254

255+
m.mu.Lock()
253256
m.finalizedWithdrawalTxns[tx.TxHash()] = tx
257+
m.mu.Unlock()
254258
}
255259

256260
return nil
@@ -275,9 +279,15 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
275279
"withdraw, unconfirmed deposits can't be withdrawn")
276280
}
277281

282+
var (
283+
deposits []*deposit.Deposit
284+
allDeposited bool
285+
allWithdrawing bool
286+
)
287+
278288
// Ensure that the deposits are in a state in which they can be
279289
// withdrawn.
280-
deposits, allDeposited := m.cfg.DepositManager.AllOutpointsActiveDeposits(
290+
deposits, allDeposited = m.cfg.DepositManager.AllOutpointsActiveDeposits(
281291
outpoints, deposit.Deposited,
282292
)
283293

@@ -286,7 +296,7 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
286296
// requesting a fee bump, if not we'll return an error as we only allow
287297
// fee bumping deposits in state Withdrawing.
288298
if !allDeposited {
289-
deposits, allWithdrawing := m.cfg.DepositManager.AllOutpointsActiveDeposits(
299+
deposits, allWithdrawing = m.cfg.DepositManager.AllOutpointsActiveDeposits(
290300
outpoints, deposit.Withdrawing,
291301
)
292302

@@ -308,6 +318,30 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
308318
"previous withdrawal tx hash")
309319
}
310320
}
321+
322+
// We also avoid that the user selects a subset of previously
323+
// clustered deposits for a fee bump. This would result in a
324+
// different transaction shape.
325+
allDeposits, err := m.cfg.DepositManager.GetActiveDepositsInState(
326+
deposit.Withdrawing,
327+
)
328+
if err != nil {
329+
return "", "", err
330+
}
331+
332+
allDepositsWithHash := make(map[chainhash.Hash][]*deposit.Deposit)
333+
for _, d := range allDeposits {
334+
if d.FinalizedWithdrawalTx.TxHash() == hash {
335+
allDepositsWithHash[hash] = append(
336+
allDepositsWithHash[hash], d,
337+
)
338+
}
339+
}
340+
341+
if len(allDepositsWithHash[hash]) != len(deposits) {
342+
return "", "", fmt.Errorf("can't bump fee for subset " +
343+
"of clustered deposits")
344+
}
311345
}
312346

313347
var (
@@ -350,27 +384,24 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
350384
return "", "", nil
351385
}
352386

353-
withdrawalPkScript, err := txscript.PayToAddrScript(withdrawalAddress)
354-
if err != nil {
355-
return "", "", err
356-
}
357-
358-
err = m.handleWithdrawal(
359-
ctx, deposits, finalizedTx.TxHash(), withdrawalPkScript,
360-
)
387+
err = m.handleWithdrawal(ctx, deposits, finalizedTx.TxHash())
361388
if err != nil {
362389
return "", "", err
363390
}
364391

365392
// If a previous withdrawal existed across the selected deposits, and
366-
// it isn't the same as the new withdrawal, we'll stop monitoring the
367-
// previous withdrawal and remove it from the finalized withdrawals.
393+
// it isn't the same as the new withdrawal, we remove it from the
394+
// finalized withdrawals to stop republishing it, but we keep the
395+
// goroutine in handleWithdrawal running to monitor the potential
396+
// confirmation of the previous withdrawal.
368397
deposits[0].Lock()
369398
prevTx := deposits[0].FinalizedWithdrawalTx
370399
deposits[0].Unlock()
371400

372401
if prevTx != nil && prevTx.TxHash() != finalizedTx.TxHash() {
402+
m.mu.Lock()
373403
delete(m.finalizedWithdrawalTxns, prevTx.TxHash())
404+
m.mu.Unlock()
374405
}
375406

376407
// Attach the finalized withdrawal tx to the deposits. After a client
@@ -383,7 +414,9 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
383414
d.Unlock()
384415
}
385416

417+
m.mu.Lock()
386418
m.finalizedWithdrawalTxns[finalizedTx.TxHash()] = finalizedTx
419+
m.mu.Unlock()
387420

388421
// Transition the deposits to the withdrawing state. This updates each
389422
// deposits withdrawal address. If a transition fails, we'll return an
@@ -529,20 +562,36 @@ func (m *Manager) publishFinalizedWithdrawalTx(ctx context.Context,
529562
}
530563

531564
func (m *Manager) handleWithdrawal(ctx context.Context,
532-
deposits []*deposit.Deposit, txHash chainhash.Hash,
533-
withdrawalPkScript []byte) error {
565+
deposits []*deposit.Deposit, txHash chainhash.Hash) error {
566+
567+
staticAddress, err := m.cfg.AddressManager.GetStaticAddress(ctx)
568+
if err != nil {
569+
log.Errorf("error retrieving taproot address %w", err)
570+
571+
return fmt.Errorf("withdrawal failed")
572+
}
534573

535-
confChan, errChan, err := m.cfg.ChainNotifier.RegisterConfirmationsNtfn(
536-
ctx, &txHash, withdrawalPkScript, MinConfs,
537-
int32(m.initiationHeight.Load()),
574+
address, err := btcutil.NewAddressTaproot(
575+
schnorr.SerializePubKey(staticAddress.TaprootKey),
576+
m.cfg.ChainParams,
538577
)
539578
if err != nil {
540579
return err
541580
}
542581

582+
script, err := txscript.PayToAddrScript(address)
583+
if err != nil {
584+
return err
585+
}
586+
587+
d := deposits[0]
588+
spentChan, errChan, err := m.cfg.ChainNotifier.RegisterSpendNtfn(
589+
ctx, &d.OutPoint, script, int32(d.ConfirmationHeight),
590+
)
591+
543592
go func() {
544593
select {
545-
case <-confChan:
594+
case <-spentChan:
546595
err = m.cfg.DepositManager.TransitionDeposits(
547596
ctx, deposits, deposit.OnWithdrawn,
548597
deposit.Withdrawn,
@@ -552,10 +601,11 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
552601
err)
553602
}
554603

555-
// Remove the withdrawal from the active withdrawals and
556-
// remove its finalized to stop republishing it on block
557-
// arrivals.
604+
// Remove the withdrawal tx from the active withdrawals
605+
// to stop republishing it on block arrivals.
606+
m.mu.Lock()
558607
delete(m.finalizedWithdrawalTxns, txHash)
608+
m.mu.Unlock()
559609

560610
case err := <-errChan:
561611
log.Errorf("Error waiting for confirmation: %v", err)
@@ -963,7 +1013,14 @@ func (m *Manager) toPrevOuts(deposits []*deposit.Deposit,
9631013
}
9641014

9651015
func (m *Manager) republishWithdrawals(ctx context.Context) error {
966-
for _, finalizedTx := range m.finalizedWithdrawalTxns {
1016+
m.mu.Lock()
1017+
txns := make([]*wire.MsgTx, 0, len(m.finalizedWithdrawalTxns))
1018+
for _, tx := range m.finalizedWithdrawalTxns {
1019+
txns = append(txns, tx)
1020+
}
1021+
m.mu.Unlock()
1022+
1023+
for _, finalizedTx := range txns {
9671024
if finalizedTx == nil {
9681025
log.Warnf("Finalized withdrawal tx is nil")
9691026
continue

0 commit comments

Comments
 (0)