@@ -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,37 @@ 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+ }
283317 }
284318
285319 var (
@@ -313,6 +347,41 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
313347 return "" , "" , err
314348 }
315349
350+ published , err := m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
351+ if err != nil {
352+ return "" , "" , err
353+ }
354+
355+ if ! published {
356+ return "" , "" , nil
357+ }
358+
359+ withdrawalPkScript , err := txscript .PayToAddrScript (withdrawalAddress )
360+ if err != nil {
361+ return "" , "" , err
362+ }
363+
364+ err = m .handleWithdrawal (
365+ ctx , deposits , finalizedTx .TxHash (), withdrawalPkScript ,
366+ )
367+ if err != nil {
368+ return "" , "" , err
369+ }
370+
371+ // If a previous withdrawal existed across the selected deposits, and
372+ // it isn't the same as the new withdrawal, we'll stop monitoring the
373+ // previous withdrawal and remove it from the finalized withdrawals.
374+ deposits [0 ].Lock ()
375+ prevTx := deposits [0 ].FinalizedWithdrawalTx
376+ deposits [0 ].Unlock ()
377+
378+ if prevTx != nil && prevTx .TxHash () != finalizedTx .TxHash () {
379+ quitChan := m .withdrawalHandlerQuitChans [prevTx .TxHash ()]
380+ close (quitChan )
381+ delete (m .withdrawalHandlerQuitChans , prevTx .TxHash ())
382+ delete (m .finalizedWithdrawalTxns , prevTx .TxHash ())
383+ }
384+
316385 // Attach the finalized withdrawal tx to the deposits. After a client
317386 // restart we can use this address as an indicator to republish the
318387 // withdrawal tx and continue the withdrawal.
@@ -323,6 +392,8 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
323392 d .Unlock ()
324393 }
325394
395+ m .finalizedWithdrawalTxns [finalizedTx .TxHash ()] = finalizedTx
396+
326397 // Transition the deposits to the withdrawing state. This updates each
327398 // deposits withdrawal address. If a transition fails, we'll return an
328399 // error and abort the withdrawal. An error in transition is likely due
@@ -335,25 +406,14 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
335406 return "" , "" , err
336407 }
337408
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
409+ // Update the deposits in the database.
410+ for _ , d := range deposits {
411+ err = m .cfg .DepositManager .UpdateDeposit (ctx , d )
412+ if err != nil {
413+ return "" , "" , err
414+ }
353415 }
354416
355- m .finalizedWithdrawalTxns [finalizedTx .TxHash ()] = finalizedTx
356-
357417 return finalizedTx .TxID (), withdrawalAddress .String (), nil
358418}
359419
@@ -450,27 +510,31 @@ func (m *Manager) createFinalizedWithdrawalTx(ctx context.Context,
450510}
451511
452512func (m * Manager ) publishFinalizedWithdrawalTx (ctx context.Context ,
453- tx * wire.MsgTx ) error {
513+ tx * wire.MsgTx ) ( bool , error ) {
454514
455515 if tx == nil {
456- return errors .New ("can't publish, finalized withdrawal tx is " +
457- "nil" )
516+ return false , errors .New ("can't publish, finalized " +
517+ "withdrawal tx is nil" )
458518 }
459519
460520 txLabel := fmt .Sprintf ("deposit-withdrawal-%v" , tx .TxHash ())
461521
462522 // Publish the withdrawal sweep transaction.
463523 err := m .cfg .WalletKit .PublishTransaction (ctx , tx , txLabel )
464-
465524 if err != nil {
466- if ! strings .Contains (err .Error (), "output already spent" ) {
467- log .Errorf ("%v: %v" , txLabel , err )
525+ if ! strings .Contains (err .Error (), "output already spent" ) &&
526+ ! strings .Contains (err .Error (), "insufficient fee" ) {
527+
528+ return false , err
529+ } else {
530+ return false , nil
468531 }
532+ } else {
533+ log .Debugf ("published deposit withdrawal with txid: %v" ,
534+ tx .TxHash ())
469535 }
470536
471- log .Debugf ("published deposit withdrawal with txid: %v" , tx .TxHash ())
472-
473- return nil
537+ return true , nil
474538}
475539
476540func (m * Manager ) handleWithdrawal (ctx context.Context ,
@@ -485,6 +549,13 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
485549 return err
486550 }
487551
552+ // Create a new quit chan for this set of deposits under the same
553+ // withdrawal tx hash. If a new withdrawal is requested the quit chan
554+ // is closed in favor of a new one, to start monitoring the new
555+ // withdrawal transaction.
556+ m .withdrawalHandlerQuitChans [txHash ] = make (chan struct {})
557+ quitChan := m .withdrawalHandlerQuitChans [txHash ]
558+
488559 go func () {
489560 select {
490561 case <- confChan :
@@ -502,6 +573,12 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
502573 // arrivals.
503574 delete (m .finalizedWithdrawalTxns , txHash )
504575
576+ case <- quitChan :
577+ log .Debugf ("Exiting withdrawal handler for tx %v" ,
578+ txHash )
579+
580+ return
581+
505582 case err := <- errChan :
506583 log .Errorf ("Error waiting for confirmation: %v" , err )
507584
@@ -914,7 +991,7 @@ func (m *Manager) republishWithdrawals(ctx context.Context) error {
914991 continue
915992 }
916993
917- err := m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
994+ _ , err := m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
918995 if err != nil {
919996 log .Errorf ("Error republishing withdrawal: %v" , err )
920997
0 commit comments