Skip to content

Commit 3942982

Browse files
committed
staticaddr: lock deposit reads while withdrawing
1 parent f3854c7 commit 3942982

File tree

1 file changed

+50
-18
lines changed

1 file changed

+50
-18
lines changed

staticaddr/withdraw/manager.go

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ var (
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.
9499
type 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

221226
func (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.
299304
func (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

Comments
 (0)