@@ -25,10 +25,11 @@ import (
2525)
2626
2727var (
28- // ErrWithdrawingInactiveDeposits is returned when the user tries to
29- // withdraw inactive deposits.
30- ErrWithdrawingInactiveDeposits = errors .New ("deposits to be " +
31- "withdrawn are unknown or inactive" )
28+ // ErrWithdrawingMixedDeposits is returned when a withdrawal is
29+ // requested for deposits in different states.
30+ ErrWithdrawingMixedDeposits = errors .New ("need to withdraw deposits " +
31+ "having the same state, either all deposited or all " +
32+ "withdrawing" )
3233
3334 // MinConfs is the minimum number of confirmations we require for a
3435 // deposit to be considered withdrawn.
@@ -111,17 +112,25 @@ type Manager struct {
111112 // finalizedWithdrawalTx are the finalized withdrawal transactions that
112113 // are published to the network and re-published on block arrivals.
113114 finalizedWithdrawalTxns map [chainhash.Hash ]* wire.MsgTx
115+
116+ // withdrawalHandlerQuitChans is a map of quit channels for each
117+ // withdrawal transaction. The quit channels are used to stop the
118+ // withdrawal handler for a specific withdrawal transaction, e.g. if
119+ // a new rbf'd transaction has to be monitored for confirmation in
120+ // favor of the previous one.
121+ withdrawalHandlerQuitChans map [chainhash.Hash ]chan struct {}
114122}
115123
116124// NewManager creates a new deposit withdrawal manager.
117125func NewManager (cfg * ManagerConfig ) * Manager {
118126 return & Manager {
119- cfg : cfg ,
120- initChan : make (chan struct {}),
121- finalizedWithdrawalTxns : make (map [chainhash.Hash ]* wire.MsgTx ),
122- exitChan : make (chan struct {}),
123- newWithdrawalRequestChan : make (chan newWithdrawalRequest ),
124- errChan : make (chan error ),
127+ cfg : cfg ,
128+ initChan : make (chan struct {}),
129+ finalizedWithdrawalTxns : make (map [chainhash.Hash ]* wire.MsgTx ),
130+ exitChan : make (chan struct {}),
131+ newWithdrawalRequestChan : make (chan newWithdrawalRequest ),
132+ errChan : make (chan error ),
133+ withdrawalHandlerQuitChans : make (map [chainhash.Hash ]chan struct {}),
125134 }
126135}
127136
@@ -235,7 +244,7 @@ func (m *Manager) recoverWithdrawals(ctx context.Context) error {
235244 return err
236245 }
237246
238- err = m .publishFinalizedWithdrawalTx (ctx , tx )
247+ _ , err : = m .publishFinalizedWithdrawalTx (ctx , tx )
239248 if err != nil {
240249 return err
241250 }
@@ -274,12 +283,38 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
274283
275284 // Ensure that the deposits are in a state in which they can be
276285 // withdrawn.
277- deposits , allActive := m .cfg .DepositManager .AllOutpointsActiveDeposits (
286+ deposits , allDeposited := m .cfg .DepositManager .AllOutpointsActiveDeposits (
278287 outpoints , deposit .Deposited ,
279288 )
280289
281- if ! allActive {
282- return "" , "" , ErrWithdrawingInactiveDeposits
290+ // If not all passed outpoints are in state Deposited, we'll check if
291+ // they are all in state Withdrawing. If they are, then the user is
292+ // requesting a fee bump, if not we'll return an error as we only allow
293+ // fee bumping deposits in state Withdrawing.
294+ if ! allDeposited {
295+ deposits , allWithdrawing := m .cfg .DepositManager .AllOutpointsActiveDeposits (
296+ outpoints , deposit .Withdrawing ,
297+ )
298+
299+ if ! allWithdrawing {
300+ return "" , "" , ErrWithdrawingMixedDeposits
301+ }
302+
303+ // If a republishing of an existing withdrawal is requested we
304+ // ensure that all deposits remain clustered in the context of
305+ // the same withdrawal by checking if they have the same
306+ // previous withdrawal tx hash.
307+ // This ensures that the shape of the transaction stays the
308+ // same.
309+ hash := deposits [0 ].FinalizedWithdrawalTx .TxHash ()
310+ for i := 1 ; i < len (deposits ); i ++ {
311+ if deposits [i ].FinalizedWithdrawalTx .TxHash () != hash {
312+ return "" , "" , fmt .Errorf ("can't bump fee " +
313+ "for deposits with different " +
314+ "previous withdrawal tx hash" )
315+ }
316+ }
317+
283318 }
284319
285320 var (
@@ -313,6 +348,41 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
313348 return "" , "" , err
314349 }
315350
351+ published , err := m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
352+ if err != nil {
353+ return "" , "" , err
354+ }
355+
356+ if ! published {
357+ return "" , "" , nil
358+ }
359+
360+ withdrawalPkScript , err := txscript .PayToAddrScript (withdrawalAddress )
361+ if err != nil {
362+ return "" , "" , err
363+ }
364+
365+ err = m .handleWithdrawal (
366+ ctx , deposits , finalizedTx .TxHash (), withdrawalPkScript ,
367+ )
368+ if err != nil {
369+ return "" , "" , err
370+ }
371+
372+ // If a previous withdrawal existed across the selected deposits, and
373+ // it isn't the same as the new withdrawal, we'll stop monitoring the
374+ // previous withdrawal and remove it from the finalized withdrawals.
375+ deposits [0 ].Lock ()
376+ prevTx := deposits [0 ].FinalizedWithdrawalTx
377+ deposits [0 ].Unlock ()
378+
379+ if prevTx != nil && prevTx .TxHash () != finalizedTx .TxHash () {
380+ quitChan := m .withdrawalHandlerQuitChans [prevTx .TxHash ()]
381+ close (quitChan )
382+ delete (m .withdrawalHandlerQuitChans , prevTx .TxHash ())
383+ delete (m .finalizedWithdrawalTxns , prevTx .TxHash ())
384+ }
385+
316386 // Attach the finalized withdrawal tx to the deposits. After a client
317387 // restart we can use this address as an indicator to republish the
318388 // withdrawal tx and continue the withdrawal.
@@ -323,6 +393,8 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
323393 d .Unlock ()
324394 }
325395
396+ m .finalizedWithdrawalTxns [finalizedTx .TxHash ()] = finalizedTx
397+
326398 // Transition the deposits to the withdrawing state. This updates each
327399 // deposits withdrawal address. If a transition fails, we'll return an
328400 // error and abort the withdrawal. An error in transition is likely due
@@ -335,25 +407,14 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
335407 return "" , "" , err
336408 }
337409
338- err = m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
339- if err != nil {
340- return "" , "" , err
341- }
342-
343- withdrawalPkScript , err := txscript .PayToAddrScript (withdrawalAddress )
344- if err != nil {
345- return "" , "" , err
346- }
347-
348- err = m .handleWithdrawal (
349- ctx , deposits , finalizedTx .TxHash (), withdrawalPkScript ,
350- )
351- if err != nil {
352- return "" , "" , err
410+ // Update the deposits in the database.
411+ for _ , d := range deposits {
412+ err = m .cfg .DepositManager .UpdateDeposit (ctx , d )
413+ if err != nil {
414+ return "" , "" , err
415+ }
353416 }
354417
355- m .finalizedWithdrawalTxns [finalizedTx .TxHash ()] = finalizedTx
356-
357418 return finalizedTx .TxID (), withdrawalAddress .String (), nil
358419}
359420
@@ -450,27 +511,31 @@ func (m *Manager) createFinalizedWithdrawalTx(ctx context.Context,
450511}
451512
452513func (m * Manager ) publishFinalizedWithdrawalTx (ctx context.Context ,
453- tx * wire.MsgTx ) error {
514+ tx * wire.MsgTx ) ( bool , error ) {
454515
455516 if tx == nil {
456- return errors .New ("can't publish, finalized withdrawal tx is " +
457- "nil" )
517+ return false , errors .New ("can't publish, finalized " +
518+ "withdrawal tx is nil" )
458519 }
459520
460521 txLabel := fmt .Sprintf ("deposit-withdrawal-%v" , tx .TxHash ())
461522
462523 // Publish the withdrawal sweep transaction.
463524 err := m .cfg .WalletKit .PublishTransaction (ctx , tx , txLabel )
464-
465525 if err != nil {
466- if ! strings .Contains (err .Error (), "output already spent" ) {
467- log .Errorf ("%v: %v" , txLabel , err )
526+ if ! strings .Contains (err .Error (), "output already spent" ) &&
527+ ! strings .Contains (err .Error (), "insufficient fee" ) {
528+
529+ return false , err
530+ } else {
531+ return false , nil
468532 }
533+ } else {
534+ log .Debugf ("published deposit withdrawal with txid: %v" ,
535+ tx .TxHash ())
469536 }
470537
471- log .Debugf ("published deposit withdrawal with txid: %v" , tx .TxHash ())
472-
473- return nil
538+ return true , nil
474539}
475540
476541func (m * Manager ) handleWithdrawal (ctx context.Context ,
@@ -485,6 +550,13 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
485550 return err
486551 }
487552
553+ // Create a new quit chan for this set of deposits under the same
554+ // withdrawal tx hash. If a new withdrawal is requested the quit chan
555+ // is closed in favor of a new one, to start monitoring the new
556+ // withdrawal transaction.
557+ m .withdrawalHandlerQuitChans [txHash ] = make (chan struct {})
558+ quitChan := m .withdrawalHandlerQuitChans [txHash ]
559+
488560 go func () {
489561 select {
490562 case <- confChan :
@@ -502,6 +574,12 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
502574 // arrivals.
503575 delete (m .finalizedWithdrawalTxns , txHash )
504576
577+ case <- quitChan :
578+ log .Debugf ("Exiting withdrawal handler for tx %v" ,
579+ txHash )
580+
581+ return
582+
505583 case err := <- errChan :
506584 log .Errorf ("Error waiting for confirmation: %v" , err )
507585
@@ -914,7 +992,7 @@ func (m *Manager) republishWithdrawals(ctx context.Context) error {
914992 continue
915993 }
916994
917- err := m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
995+ _ , err := m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
918996 if err != nil {
919997 log .Errorf ("Error republishing withdrawal: %v" , err )
920998
0 commit comments