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 {}
@@ -113,25 +117,17 @@ type Manager struct {
113117 // finalizedWithdrawalTx are the finalized withdrawal transactions that
114118 // are published to the network and re-published on block arrivals.
115119 finalizedWithdrawalTxns map [chainhash.Hash ]* wire.MsgTx
116-
117- // withdrawalHandlerQuitChans is a map of quit channels for each
118- // withdrawal transaction. The quit channels are used to stop the
119- // withdrawal handler for a specific withdrawal transaction, e.g. if
120- // a new rbf'd transaction has to be monitored for confirmation in
121- // favor of the previous one.
122- withdrawalHandlerQuitChans map [chainhash.Hash ]chan struct {}
123120}
124121
125122// NewManager creates a new deposit withdrawal manager.
126123func NewManager (cfg * ManagerConfig , currentHeight uint32 ) * Manager {
127124 m := & Manager {
128- cfg : cfg ,
129- initChan : make (chan struct {}),
130- finalizedWithdrawalTxns : make (map [chainhash.Hash ]* wire.MsgTx ),
131- exitChan : make (chan struct {}),
132- newWithdrawalRequestChan : make (chan newWithdrawalRequest ),
133- errChan : make (chan error ),
134- withdrawalHandlerQuitChans : make (map [chainhash.Hash ]chan struct {}),
125+ cfg : cfg ,
126+ initChan : make (chan struct {}),
127+ finalizedWithdrawalTxns : make (map [chainhash.Hash ]* wire.MsgTx ),
128+ exitChan : make (chan struct {}),
129+ newWithdrawalRequestChan : make (chan newWithdrawalRequest ),
130+ errChan : make (chan error ),
135131 }
136132 m .initiationHeight .Store (currentHeight )
137133
@@ -251,14 +247,14 @@ func (m *Manager) recoverWithdrawals(ctx context.Context) error {
251247 return err
252248 }
253249
254- err = m .handleWithdrawal (
255- ctx , deposits , tx .TxHash (), tx .TxOut [0 ].PkScript ,
256- )
250+ err = m .handleWithdrawal (ctx , deposits , tx .TxHash ())
257251 if err != nil {
258252 return err
259253 }
260254
255+ m .mu .Lock ()
261256 m .finalizedWithdrawalTxns [tx .TxHash ()] = tx
257+ m .mu .Unlock ()
262258 }
263259
264260 return nil
@@ -283,9 +279,15 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
283279 "withdraw, unconfirmed deposits can't be withdrawn" )
284280 }
285281
282+ var (
283+ deposits []* deposit.Deposit
284+ allDeposited bool
285+ allWithdrawing bool
286+ )
287+
286288 // Ensure that the deposits are in a state in which they can be
287289 // withdrawn.
288- deposits , allDeposited : = m .cfg .DepositManager .AllOutpointsActiveDeposits (
290+ deposits , allDeposited = m .cfg .DepositManager .AllOutpointsActiveDeposits (
289291 outpoints , deposit .Deposited ,
290292 )
291293
@@ -294,7 +296,7 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
294296 // requesting a fee bump, if not we'll return an error as we only allow
295297 // fee bumping deposits in state Withdrawing.
296298 if ! allDeposited {
297- deposits , allWithdrawing : = m .cfg .DepositManager .AllOutpointsActiveDeposits (
299+ deposits , allWithdrawing = m .cfg .DepositManager .AllOutpointsActiveDeposits (
298300 outpoints , deposit .Withdrawing ,
299301 )
300302
@@ -316,6 +318,30 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
316318 "previous withdrawal tx hash" )
317319 }
318320 }
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+ }
319345 }
320346
321347 var (
@@ -358,30 +384,24 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
358384 return "" , "" , nil
359385 }
360386
361- withdrawalPkScript , err := txscript .PayToAddrScript (withdrawalAddress )
362- if err != nil {
363- return "" , "" , err
364- }
365-
366- err = m .handleWithdrawal (
367- ctx , deposits , finalizedTx .TxHash (), withdrawalPkScript ,
368- )
387+ err = m .handleWithdrawal (ctx , deposits , finalizedTx .TxHash ())
369388 if err != nil {
370389 return "" , "" , err
371390 }
372391
373392 // If a previous withdrawal existed across the selected deposits, and
374- // it isn't the same as the new withdrawal, we'll stop monitoring the
375- // 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.
376397 deposits [0 ].Lock ()
377398 prevTx := deposits [0 ].FinalizedWithdrawalTx
378399 deposits [0 ].Unlock ()
379400
380401 if prevTx != nil && prevTx .TxHash () != finalizedTx .TxHash () {
381- quitChan := m .withdrawalHandlerQuitChans [prevTx .TxHash ()]
382- close (quitChan )
383- delete (m .withdrawalHandlerQuitChans , prevTx .TxHash ())
402+ m .mu .Lock ()
384403 delete (m .finalizedWithdrawalTxns , prevTx .TxHash ())
404+ m .mu .Unlock ()
385405 }
386406
387407 // Attach the finalized withdrawal tx to the deposits. After a client
@@ -394,7 +414,9 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
394414 d .Unlock ()
395415 }
396416
417+ m .mu .Lock ()
397418 m .finalizedWithdrawalTxns [finalizedTx .TxHash ()] = finalizedTx
419+ m .mu .Unlock ()
398420
399421 // Transition the deposits to the withdrawing state. This updates each
400422 // deposits withdrawal address. If a transition fails, we'll return an
@@ -540,27 +562,36 @@ func (m *Manager) publishFinalizedWithdrawalTx(ctx context.Context,
540562}
541563
542564func (m * Manager ) handleWithdrawal (ctx context.Context ,
543- deposits []* deposit.Deposit , txHash chainhash.Hash ,
544- withdrawalPkScript []byte ) error {
565+ deposits []* deposit.Deposit , txHash chainhash.Hash ) error {
545566
546- confChan , errChan , err := m .cfg .ChainNotifier .RegisterConfirmationsNtfn (
547- ctx , & txHash , withdrawalPkScript , MinConfs ,
548- int32 (m .initiationHeight .Load ()),
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+ }
573+
574+ address , err := btcutil .NewAddressTaproot (
575+ schnorr .SerializePubKey (staticAddress .TaprootKey ),
576+ m .cfg .ChainParams ,
549577 )
550578 if err != nil {
551579 return err
552580 }
553581
554- // Create a new quit chan for this set of deposits under the same
555- // withdrawal tx hash. If a new withdrawal is requested the quit chan
556- // is closed in favor of a new one, to start monitoring the new
557- // withdrawal transaction.
558- m .withdrawalHandlerQuitChans [txHash ] = make (chan struct {})
559- quitChan := m .withdrawalHandlerQuitChans [txHash ]
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+ )
560591
561592 go func () {
562593 select {
563- case <- confChan :
594+ case <- spentChan :
564595 err = m .cfg .DepositManager .TransitionDeposits (
565596 ctx , deposits , deposit .OnWithdrawn ,
566597 deposit .Withdrawn ,
@@ -570,16 +601,11 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
570601 err )
571602 }
572603
573- // Remove the withdrawal from the active withdrawals and
574- // remove its finalized to stop republishing it on block
575- // arrivals.
604+ // Remove the withdrawal tx from the active withdrawals
605+ // to stop republishing it on block arrivals.
606+ m . mu . Lock ()
576607 delete (m .finalizedWithdrawalTxns , txHash )
577-
578- case <- quitChan :
579- log .Debugf ("Exiting withdrawal handler for tx %v" ,
580- txHash )
581-
582- return
608+ m .mu .Unlock ()
583609
584610 case err := <- errChan :
585611 log .Errorf ("Error waiting for confirmation: %v" , err )
@@ -987,7 +1013,14 @@ func (m *Manager) toPrevOuts(deposits []*deposit.Deposit,
9871013}
9881014
9891015func (m * Manager ) republishWithdrawals (ctx context.Context ) error {
990- 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 {
9911024 if finalizedTx == nil {
9921025 log .Warnf ("Finalized withdrawal tx is nil" )
9931026 continue
0 commit comments