@@ -26,10 +26,11 @@ import (
2626)
2727
2828var (
29- // ErrWithdrawingInactiveDeposits is returned when the user tries to
30- // withdraw inactive deposits.
31- ErrWithdrawingInactiveDeposits = errors .New ("deposits to be " +
32- "withdrawn are unknown or inactive" )
29+ // ErrWithdrawingMixedDeposits is returned when a withdrawal is
30+ // requested for deposits in different states.
31+ ErrWithdrawingMixedDeposits = errors .New ("need to withdraw deposits " +
32+ "having the same state, either all deposited or all " +
33+ "withdrawing" )
3334
3435 // MinConfs is the minimum number of confirmations we require for a
3536 // deposit to be considered withdrawn.
@@ -112,17 +113,25 @@ type Manager struct {
112113 // finalizedWithdrawalTx are the finalized withdrawal transactions that
113114 // are published to the network and re-published on block arrivals.
114115 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 {}
115123}
116124
117125// NewManager creates a new deposit withdrawal manager.
118126func NewManager (cfg * ManagerConfig , currentHeight uint32 ) * Manager {
119127 m := & Manager {
120- cfg : cfg ,
121- initChan : make (chan struct {}),
122- finalizedWithdrawalTxns : make (map [chainhash.Hash ]* wire.MsgTx ),
123- exitChan : make (chan struct {}),
124- newWithdrawalRequestChan : make (chan newWithdrawalRequest ),
125- errChan : make (chan error ),
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 {}),
126135 }
127136 m .initiationHeight .Store (currentHeight )
128137
@@ -237,7 +246,7 @@ func (m *Manager) recoverWithdrawals(ctx context.Context) error {
237246 return err
238247 }
239248
240- err = m .publishFinalizedWithdrawalTx (ctx , tx )
249+ _ , err : = m .publishFinalizedWithdrawalTx (ctx , tx )
241250 if err != nil {
242251 return err
243252 }
@@ -276,12 +285,37 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
276285
277286 // Ensure that the deposits are in a state in which they can be
278287 // withdrawn.
279- deposits , allActive := m .cfg .DepositManager .AllOutpointsActiveDeposits (
288+ deposits , allDeposited := m .cfg .DepositManager .AllOutpointsActiveDeposits (
280289 outpoints , deposit .Deposited ,
281290 )
282291
283- if ! allActive {
284- return "" , "" , ErrWithdrawingInactiveDeposits
292+ // If not all passed outpoints are in state Deposited, we'll check if
293+ // they are all in state Withdrawing. If they are, then the user is
294+ // requesting a fee bump, if not we'll return an error as we only allow
295+ // fee bumping deposits in state Withdrawing.
296+ if ! allDeposited {
297+ deposits , allWithdrawing := m .cfg .DepositManager .AllOutpointsActiveDeposits (
298+ outpoints , deposit .Withdrawing ,
299+ )
300+
301+ if ! allWithdrawing {
302+ return "" , "" , ErrWithdrawingMixedDeposits
303+ }
304+
305+ // If a republishing of an existing withdrawal is requested we
306+ // ensure that all deposits remain clustered in the context of
307+ // the same withdrawal by checking if they have the same
308+ // previous withdrawal tx hash.
309+ // This ensures that the shape of the transaction stays the
310+ // same.
311+ hash := deposits [0 ].FinalizedWithdrawalTx .TxHash ()
312+ for i := 1 ; i < len (deposits ); i ++ {
313+ if deposits [i ].FinalizedWithdrawalTx .TxHash () != hash {
314+ return "" , "" , fmt .Errorf ("can't bump fee " +
315+ "for deposits with different " +
316+ "previous withdrawal tx hash" )
317+ }
318+ }
285319 }
286320
287321 var (
@@ -315,6 +349,41 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
315349 return "" , "" , err
316350 }
317351
352+ published , err := m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
353+ if err != nil {
354+ return "" , "" , err
355+ }
356+
357+ if ! published {
358+ return "" , "" , nil
359+ }
360+
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+ )
369+ if err != nil {
370+ return "" , "" , err
371+ }
372+
373+ // 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.
376+ deposits [0 ].Lock ()
377+ prevTx := deposits [0 ].FinalizedWithdrawalTx
378+ deposits [0 ].Unlock ()
379+
380+ if prevTx != nil && prevTx .TxHash () != finalizedTx .TxHash () {
381+ quitChan := m .withdrawalHandlerQuitChans [prevTx .TxHash ()]
382+ close (quitChan )
383+ delete (m .withdrawalHandlerQuitChans , prevTx .TxHash ())
384+ delete (m .finalizedWithdrawalTxns , prevTx .TxHash ())
385+ }
386+
318387 // Attach the finalized withdrawal tx to the deposits. After a client
319388 // restart we can use this address as an indicator to republish the
320389 // withdrawal tx and continue the withdrawal.
@@ -325,6 +394,8 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
325394 d .Unlock ()
326395 }
327396
397+ m .finalizedWithdrawalTxns [finalizedTx .TxHash ()] = finalizedTx
398+
328399 // Transition the deposits to the withdrawing state. This updates each
329400 // deposits withdrawal address. If a transition fails, we'll return an
330401 // error and abort the withdrawal. An error in transition is likely due
@@ -337,25 +408,14 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
337408 return "" , "" , err
338409 }
339410
340- err = m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
341- if err != nil {
342- return "" , "" , err
343- }
344-
345- withdrawalPkScript , err := txscript .PayToAddrScript (withdrawalAddress )
346- if err != nil {
347- return "" , "" , err
348- }
349-
350- err = m .handleWithdrawal (
351- ctx , deposits , finalizedTx .TxHash (), withdrawalPkScript ,
352- )
353- if err != nil {
354- return "" , "" , err
411+ // Update the deposits in the database.
412+ for _ , d := range deposits {
413+ err = m .cfg .DepositManager .UpdateDeposit (ctx , d )
414+ if err != nil {
415+ return "" , "" , err
416+ }
355417 }
356418
357- m .finalizedWithdrawalTxns [finalizedTx .TxHash ()] = finalizedTx
358-
359419 return finalizedTx .TxID (), withdrawalAddress .String (), nil
360420}
361421
@@ -452,27 +512,31 @@ func (m *Manager) createFinalizedWithdrawalTx(ctx context.Context,
452512}
453513
454514func (m * Manager ) publishFinalizedWithdrawalTx (ctx context.Context ,
455- tx * wire.MsgTx ) error {
515+ tx * wire.MsgTx ) ( bool , error ) {
456516
457517 if tx == nil {
458- return errors .New ("can't publish, finalized withdrawal tx is " +
459- "nil" )
518+ return false , errors .New ("can't publish, finalized " +
519+ "withdrawal tx is nil" )
460520 }
461521
462522 txLabel := fmt .Sprintf ("deposit-withdrawal-%v" , tx .TxHash ())
463523
464524 // Publish the withdrawal sweep transaction.
465525 err := m .cfg .WalletKit .PublishTransaction (ctx , tx , txLabel )
466-
467526 if err != nil {
468- if ! strings .Contains (err .Error (), "output already spent" ) {
469- log .Errorf ("%v: %v" , txLabel , err )
527+ if ! strings .Contains (err .Error (), "output already spent" ) &&
528+ ! strings .Contains (err .Error (), "insufficient fee" ) {
529+
530+ return false , err
531+ } else {
532+ return false , nil
470533 }
534+ } else {
535+ log .Debugf ("published deposit withdrawal with txid: %v" ,
536+ tx .TxHash ())
471537 }
472538
473- log .Debugf ("published deposit withdrawal with txid: %v" , tx .TxHash ())
474-
475- return nil
539+ return true , nil
476540}
477541
478542func (m * Manager ) handleWithdrawal (ctx context.Context ,
@@ -487,6 +551,13 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
487551 return err
488552 }
489553
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 ]
560+
490561 go func () {
491562 select {
492563 case <- confChan :
@@ -504,6 +575,12 @@ func (m *Manager) handleWithdrawal(ctx context.Context,
504575 // arrivals.
505576 delete (m .finalizedWithdrawalTxns , txHash )
506577
578+ case <- quitChan :
579+ log .Debugf ("Exiting withdrawal handler for tx %v" ,
580+ txHash )
581+
582+ return
583+
507584 case err := <- errChan :
508585 log .Errorf ("Error waiting for confirmation: %v" , err )
509586
@@ -916,7 +993,7 @@ func (m *Manager) republishWithdrawals(ctx context.Context) error {
916993 continue
917994 }
918995
919- err := m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
996+ _ , err := m .publishFinalizedWithdrawalTx (ctx , finalizedTx )
920997 if err != nil {
921998 log .Errorf ("Error republishing withdrawal: %v" , err )
922999
0 commit comments