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
@@ -316,6 +312,30 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
316312 "previous withdrawal tx hash" )
317313 }
318314 }
315+
316+ // We also avoid that the user selects a subset of previously
317+ // clustered deposits for a fee bump. This would result in a
318+ // different transaction shape.
319+ allDeposits , err := m .cfg .DepositManager .GetActiveDepositsInState (
320+ deposit .Withdrawing ,
321+ )
322+ if err != nil {
323+ return "" , "" , err
324+ }
325+
326+ allDepositsWithHash := make (map [chainhash.Hash ][]* deposit.Deposit )
327+ for _ , d := range allDeposits {
328+ if d .FinalizedWithdrawalTx .TxHash () == hash {
329+ allDepositsWithHash [hash ] = append (
330+ allDepositsWithHash [hash ], d ,
331+ )
332+ }
333+ }
334+
335+ if len (allDepositsWithHash [hash ]) != len (deposits ) {
336+ return "" , "" , fmt .Errorf ("can't bump fee for subset " +
337+ "of clustered deposits" )
338+ }
319339 }
320340
321341 var (
@@ -358,30 +378,24 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
358378 return "" , "" , nil
359379 }
360380
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- )
381+ err = m .handleWithdrawal (ctx , deposits , finalizedTx .TxHash ())
369382 if err != nil {
370383 return "" , "" , err
371384 }
372385
373386 // 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.
387+ // it isn't the same as the new withdrawal, we remove it from the
388+ // finalized withdrawals to stop republishing it, but we keep the
389+ // goroutine in handleWithdrawal running to monitor the potential
390+ // confirmation of the previous withdrawal.
376391 deposits [0 ].Lock ()
377392 prevTx := deposits [0 ].FinalizedWithdrawalTx
378393 deposits [0 ].Unlock ()
379394
380395 if prevTx != nil && prevTx .TxHash () != finalizedTx .TxHash () {
381- quitChan := m .withdrawalHandlerQuitChans [prevTx .TxHash ()]
382- close (quitChan )
383- delete (m .withdrawalHandlerQuitChans , prevTx .TxHash ())
396+ m .mu .Lock ()
384397 delete (m .finalizedWithdrawalTxns , prevTx .TxHash ())
398+ m .mu .Unlock ()
385399 }
386400
387401 // Attach the finalized withdrawal tx to the deposits. After a client
@@ -394,7 +408,9 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
394408 d .Unlock ()
395409 }
396410
411+ m .mu .Lock ()
397412 m .finalizedWithdrawalTxns [finalizedTx .TxHash ()] = finalizedTx
413+ m .mu .Unlock ()
398414
399415 // Transition the deposits to the withdrawing state. This updates each
400416 // deposits withdrawal address. If a transition fails, we'll return an
@@ -540,27 +556,16 @@ func (m *Manager) publishFinalizedWithdrawalTx(ctx context.Context,
540556}
541557
542558func (m * Manager ) handleWithdrawal (ctx context.Context ,
543- deposits []* deposit.Deposit , txHash chainhash.Hash ,
544- withdrawalPkScript []byte ) error {
559+ deposits []* deposit.Deposit , txHash chainhash.Hash ) error {
545560
546- confChan , errChan , err := m . cfg . ChainNotifier . RegisterConfirmationsNtfn (
547- ctx , & txHash , withdrawalPkScript , MinConfs ,
548- int32 (m . initiationHeight . Load () ),
561+ d := deposits [ 0 ]
562+ spentChan , errChan , err := m . cfg . ChainNotifier . RegisterSpendNtfn (
563+ ctx , & d . OutPoint , nil , int32 (d . ConfirmationHeight ),
549564 )
550- if err != nil {
551- return err
552- }
553-
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 ]
560565
561566 go func () {
562567 select {
563- case <- confChan :
568+ case <- spentChan :
564569 err = m .cfg .DepositManager .TransitionDeposits (
565570 ctx , deposits , deposit .OnWithdrawn ,
566571 deposit .Withdrawn ,
@@ -570,16 +575,11 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
570575 err )
571576 }
572577
573- // Remove the withdrawal from the active withdrawals and
574- // remove its finalized to stop republishing it on block
575- // arrivals.
578+ // Remove the withdrawal tx from the active withdrawals
579+ // to stop republishing it on block arrivals.
580+ m . mu . Lock ()
576581 delete (m .finalizedWithdrawalTxns , txHash )
577-
578- case <- quitChan :
579- log .Debugf ("Exiting withdrawal handler for tx %v" ,
580- txHash )
581-
582- return
582+ m .mu .Unlock ()
583583
584584 case err := <- errChan :
585585 log .Errorf ("Error waiting for confirmation: %v" , err )
@@ -987,7 +987,14 @@ func (m *Manager) toPrevOuts(deposits []*deposit.Deposit,
987987}
988988
989989func (m * Manager ) republishWithdrawals (ctx context.Context ) error {
990- for _ , finalizedTx := range m .finalizedWithdrawalTxns {
990+ m .mu .Lock ()
991+ txns := make ([]* wire.MsgTx , 0 , len (m .finalizedWithdrawalTxns ))
992+ for _ , tx := range m .finalizedWithdrawalTxns {
993+ txns = append (txns , tx )
994+ }
995+ m .mu .Unlock ()
996+
997+ for _ , finalizedTx := range txns {
991998 if finalizedTx == nil {
992999 log .Warnf ("Finalized withdrawal tx is nil" )
9931000 continue
0 commit comments