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 {
9394type 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
531564func (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
9651015func (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