Skip to content

Commit 99eb4f3

Browse files
committed
staticaddr: allow rbf'ing withdrawal transactions
1 parent cecb24c commit 99eb4f3

File tree

2 files changed

+91
-35
lines changed

2 files changed

+91
-35
lines changed

staticaddr/deposit/fsm.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ func (f *FSM) DepositStatesV0() fsm.States {
297297
Withdrawing: fsm.State{
298298
Transitions: fsm.Transitions{
299299
OnWithdrawn: Withdrawn,
300+
// OnWithdrawInitiated is sent if a fee bump was
301+
// requested and the withdrawal was republished.
302+
OnWithdrawInitiated: Withdrawing,
300303
// Upon recovery, we go back to the Deposited
301304
// state. The deposit by then has a withdrawal
302305
// address stamped to it which will cause it to

staticaddr/withdraw/manager.go

Lines changed: 88 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ import (
2626
)
2727

2828
var (
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.
@@ -237,7 +238,7 @@ func (m *Manager) recoverWithdrawals(ctx context.Context) error {
237238
return err
238239
}
239240

240-
err = m.publishFinalizedWithdrawalTx(ctx, tx)
241+
_, err := m.publishFinalizedWithdrawalTx(ctx, tx)
241242
if err != nil {
242243
return err
243244
}
@@ -276,12 +277,37 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
276277

277278
// Ensure that the deposits are in a state in which they can be
278279
// withdrawn.
279-
deposits, allActive := m.cfg.DepositManager.AllOutpointsActiveDeposits(
280+
deposits, allDeposited := m.cfg.DepositManager.AllOutpointsActiveDeposits(
280281
outpoints, deposit.Deposited,
281282
)
282283

283-
if !allActive {
284-
return "", "", ErrWithdrawingInactiveDeposits
284+
// If not all passed outpoints are in state Deposited, we'll check if
285+
// they are all in state Withdrawing. If they are, then the user is
286+
// requesting a fee bump, if not we'll return an error as we only allow
287+
// fee bumping deposits in state Withdrawing.
288+
if !allDeposited {
289+
deposits, allWithdrawing := m.cfg.DepositManager.AllOutpointsActiveDeposits(
290+
outpoints, deposit.Withdrawing,
291+
)
292+
293+
if !allWithdrawing {
294+
return "", "", ErrWithdrawingMixedDeposits
295+
}
296+
297+
// If a republishing of an existing withdrawal is requested we
298+
// ensure that all deposits remain clustered in the context of
299+
// the same withdrawal by checking if they have the same
300+
// previous withdrawal tx hash.
301+
// This ensures that the shape of the transaction stays the
302+
// same.
303+
hash := deposits[0].FinalizedWithdrawalTx.TxHash()
304+
for i := 1; i < len(deposits); i++ {
305+
if deposits[i].FinalizedWithdrawalTx.TxHash() != hash {
306+
return "", "", fmt.Errorf("can't bump fee " +
307+
"for deposits with different " +
308+
"previous withdrawal tx hash")
309+
}
310+
}
285311
}
286312

287313
var (
@@ -315,6 +341,38 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
315341
return "", "", err
316342
}
317343

344+
published, err := m.publishFinalizedWithdrawalTx(ctx, finalizedTx)
345+
if err != nil {
346+
return "", "", err
347+
}
348+
349+
if !published {
350+
return "", "", nil
351+
}
352+
353+
withdrawalPkScript, err := txscript.PayToAddrScript(withdrawalAddress)
354+
if err != nil {
355+
return "", "", err
356+
}
357+
358+
err = m.handleWithdrawal(
359+
ctx, deposits, finalizedTx.TxHash(), withdrawalPkScript,
360+
)
361+
if err != nil {
362+
return "", "", err
363+
}
364+
365+
// If a previous withdrawal existed across the selected deposits, and
366+
// it isn't the same as the new withdrawal, we'll stop monitoring the
367+
// previous withdrawal and remove it from the finalized withdrawals.
368+
deposits[0].Lock()
369+
prevTx := deposits[0].FinalizedWithdrawalTx
370+
deposits[0].Unlock()
371+
372+
if prevTx != nil && prevTx.TxHash() != finalizedTx.TxHash() {
373+
delete(m.finalizedWithdrawalTxns, prevTx.TxHash())
374+
}
375+
318376
// Attach the finalized withdrawal tx to the deposits. After a client
319377
// restart we can use this address as an indicator to republish the
320378
// withdrawal tx and continue the withdrawal.
@@ -325,6 +383,8 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
325383
d.Unlock()
326384
}
327385

386+
m.finalizedWithdrawalTxns[finalizedTx.TxHash()] = finalizedTx
387+
328388
// Transition the deposits to the withdrawing state. This updates each
329389
// deposits withdrawal address. If a transition fails, we'll return an
330390
// error and abort the withdrawal. An error in transition is likely due
@@ -337,25 +397,14 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
337397
return "", "", err
338398
}
339399

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
400+
// Update the deposits in the database.
401+
for _, d := range deposits {
402+
err = m.cfg.DepositManager.UpdateDeposit(ctx, d)
403+
if err != nil {
404+
return "", "", err
405+
}
355406
}
356407

357-
m.finalizedWithdrawalTxns[finalizedTx.TxHash()] = finalizedTx
358-
359408
return finalizedTx.TxID(), withdrawalAddress.String(), nil
360409
}
361410

@@ -452,27 +501,31 @@ func (m *Manager) createFinalizedWithdrawalTx(ctx context.Context,
452501
}
453502

454503
func (m *Manager) publishFinalizedWithdrawalTx(ctx context.Context,
455-
tx *wire.MsgTx) error {
504+
tx *wire.MsgTx) (bool, error) {
456505

457506
if tx == nil {
458-
return errors.New("can't publish, finalized withdrawal tx is " +
459-
"nil")
507+
return false, errors.New("can't publish, finalized " +
508+
"withdrawal tx is nil")
460509
}
461510

462511
txLabel := fmt.Sprintf("deposit-withdrawal-%v", tx.TxHash())
463512

464513
// Publish the withdrawal sweep transaction.
465514
err := m.cfg.WalletKit.PublishTransaction(ctx, tx, txLabel)
466-
467515
if err != nil {
468-
if !strings.Contains(err.Error(), "output already spent") {
469-
log.Errorf("%v: %v", txLabel, err)
516+
if !strings.Contains(err.Error(), "output already spent") &&
517+
!strings.Contains(err.Error(), "insufficient fee") {
518+
519+
return false, err
520+
} else {
521+
return false, nil
470522
}
523+
} else {
524+
log.Debugf("published deposit withdrawal with txid: %v",
525+
tx.TxHash())
471526
}
472527

473-
log.Debugf("published deposit withdrawal with txid: %v", tx.TxHash())
474-
475-
return nil
528+
return true, nil
476529
}
477530

478531
func (m *Manager) handleWithdrawal(ctx context.Context,
@@ -916,7 +969,7 @@ func (m *Manager) republishWithdrawals(ctx context.Context) error {
916969
continue
917970
}
918971

919-
err := m.publishFinalizedWithdrawalTx(ctx, finalizedTx)
972+
_, err := m.publishFinalizedWithdrawalTx(ctx, finalizedTx)
920973
if err != nil {
921974
log.Errorf("Error republishing withdrawal: %v", err)
922975

0 commit comments

Comments
 (0)