4646 ErrMissingPreviousWithdrawn = errors .New ("can't bump fee for subset " +
4747 "of clustered deposits" )
4848
49+ // ErrMissingFinalizedTx is returned if previously withdrawn deposits
50+ // don't have a finalized withdrawal tx attached.
51+ ErrMissingFinalizedTx = errors .New ("deposit does not have a " +
52+ "finalized withdrawal tx, can't bump fee" )
53+
4954 // MinConfs is the minimum number of confirmations we require for a
5055 // deposit to be considered withdrawn.
5156 MinConfs int32 = 3
@@ -89,7 +94,7 @@ type ManagerConfig struct {
8994 Store * SqlStore
9095}
9196
92- // newWithdrawalRequest is used to send withdrawal request to the manager main
97+ // newWithdrawalRequest is used to send a withdrawal request to the manager main
9398// loop.
9499type newWithdrawalRequest struct {
95100 outpoints []wire.OutPoint
@@ -115,7 +120,7 @@ type Manager struct {
115120 mu sync.Mutex
116121
117122 // newWithdrawalRequestChan receives a list of outpoints that should be
118- // withdrawn. The request is forwarded to the managers main loop.
123+ // withdrawn. The request is forwarded to the managers' main loop.
119124 newWithdrawalRequestChan chan newWithdrawalRequest
120125
121126 // exitChan signals subroutines that the withdrawal manager is exiting.
@@ -219,9 +224,9 @@ func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error {
219224}
220225
221226func (m * Manager ) recoverWithdrawals (ctx context.Context ) error {
222- // To recover withdrawals we cluster those with equal withdrawal
223- // addresses and publish their withdrawal tx. Each cluster represents a
224- // separate withdrawal intent by the user.
227+ // To recover withdrawals, we cluster those with equal withdrawal
228+ // addresses and publish their withdrawal tx. Each cluster represents
229+ // one separate withdrawal intent by the user.
225230 withdrawingDeposits , err := m .cfg .DepositManager .GetActiveDepositsInState (
226231 deposit .Withdrawing ,
227232 )
@@ -294,7 +299,7 @@ func (m *Manager) recoverWithdrawals(ctx context.Context) error {
294299 return nil
295300}
296301
297- // WithdrawDeposits starts a deposits withdrawal flow. If the amount is set to 0
302+ // WithdrawDeposits starts a deposit withdrawal flow. If the amount is set to 0,
298303// the full amount of the selected deposits will be withdrawn.
299304func (m * Manager ) WithdrawDeposits (ctx context.Context ,
300305 outpoints []wire.OutPoint , destAddr string , satPerVbyte int64 ,
@@ -309,6 +314,16 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
309314 deposits []* deposit.Deposit
310315 allDeposited bool
311316 allWithdrawing bool
317+ lockDeposits = func (deposits []* deposit.Deposit ) {
318+ for _ , d := range deposits {
319+ d .Lock ()
320+ }
321+ }
322+ unlockDeposits = func (deposits []* deposit.Deposit ) {
323+ for _ , d := range deposits {
324+ d .Unlock ()
325+ }
326+ }
312327 )
313328
314329 // Ensure that the deposits are in a state in which they can be
@@ -325,20 +340,32 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
325340 deposits , allWithdrawing = m .cfg .DepositManager .AllOutpointsActiveDeposits (
326341 outpoints , deposit .Withdrawing ,
327342 )
328-
329343 if ! allWithdrawing {
330344 return "" , "" , ErrWithdrawingMixedDeposits
331345 }
346+ }
347+
348+ // Lock the deposits to prevent concurrent deposit access.
349+ lockDeposits (deposits )
350+ locked := true
351+ defer func () {
352+ if locked {
353+ unlockDeposits (deposits )
354+ }
355+ }()
332356
357+ // If the user requested a fee bump, we'll sanity check the selected
358+ // deposits to ensure that they are all part of the same withdrawal.
359+ if ! allDeposited {
333360 // Ensure that all previously withdrawn deposits reference their
334361 // finalized withdrawal tx.
335362 for _ , d := range deposits {
336363 if d .FinalizedWithdrawalTx == nil {
337- return "" , "" , ErrMissingPreviousWithdrawn
364+ return "" , "" , ErrMissingFinalizedTx
338365 }
339366 }
340367
341- // If republishing of an existing withdrawal is requested we
368+ // If republishing of an existing withdrawal is requested, we
342369 // ensure that all deposits remain clustered in the context of
343370 // the same withdrawal tx. We do this by checking that they have
344371 // the same previous withdrawal tx hash. This ensures that the
@@ -351,9 +378,9 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
351378 }
352379 }
353380
354- // We also avoid that the user selects a subset of previously
355- // clustered deposits for a fee bump. This would result in a
356- // different transaction shape.
381+ // We also prevent the user from selecting a subset of
382+ // previously clustered deposits for a fee bump. This would
383+ // result in a different transaction shape.
357384 outpointMap := make (map [wire.OutPoint ]struct {})
358385 for _ , d := range deposits {
359386 outpointMap [d .OutPoint ] = struct {}{}
@@ -443,9 +470,7 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
443470 // If a previous withdrawal existed across the selected deposits, and
444471 // it isn't the same as the new withdrawal, we remove it from the
445472 // finalized withdrawals to stop republishing it on block arrivals.
446- deposits [0 ].Lock ()
447473 prevTx := deposits [0 ].FinalizedWithdrawalTx
448- deposits [0 ].Unlock ()
449474
450475 if prevTx != nil && prevTx .TxHash () != finalizedTx .TxHash () {
451476 m .mu .Lock ()
@@ -454,13 +479,11 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
454479 }
455480
456481 // Attach the finalized withdrawal tx to the deposits. After a client
457- // restart we can use this address as an indicator to republish the
482+ // restarts, we can use this address as an indicator to republish the
458483 // withdrawal tx and continue the withdrawal.
459484 // Deposits with the same withdrawal tx are part of the same withdrawal.
460485 for _ , d := range deposits {
461- d .Lock ()
462486 d .FinalizedWithdrawalTx = finalizedTx
463- d .Unlock ()
464487 }
465488
466489 // Add the new withdrawal tx to the finalized withdrawals to republish
@@ -469,8 +492,13 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
469492 m .finalizedWithdrawalTxns [finalizedTx .TxHash ()] = finalizedTx
470493 m .mu .Unlock ()
471494
495+ // Unlock the deposits before calling TransitionDeposits, because this
496+ // method locks the deposits itself.
497+ unlockDeposits (deposits )
498+ locked = false
499+
472500 // Transition the deposits to the withdrawing state. If the user fee
473- // bumped a withdrawal this results in a NOOP transition.
501+ // bumped a withdrawal, this results in a NOOP transition.
474502 err = m .cfg .DepositManager .TransitionDeposits (
475503 ctx , deposits , deposit .OnWithdrawInitiated , deposit .Withdrawing ,
476504 )
@@ -479,6 +507,10 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
479507 err )
480508 }
481509
510+ // Re-lock the deposits.
511+ lockDeposits (deposits )
512+ locked = true
513+
482514 // Update the deposits in the database.
483515 for _ , d := range deposits {
484516 err = m .cfg .DepositManager .UpdateDeposit (ctx , d )
0 commit comments