diff --git a/staticaddr/withdraw/manager.go b/staticaddr/withdraw/manager.go index 6433b6344b..fba0ee10b0 100644 --- a/staticaddr/withdraw/manager.go +++ b/staticaddr/withdraw/manager.go @@ -11,6 +11,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/lndclient" @@ -89,14 +90,19 @@ type Manager struct { // activeWithdrawals stores pending withdrawals by their withdrawal // address. activeWithdrawals map[string][]*deposit.Deposit + + // finalizedWithdrawalTxns are the finalized withdrawal transactions + // that are published to the network and re-published on block arrivals. + finalizedWithdrawalTxns map[chainhash.Hash]*wire.MsgTx } // NewManager creates a new deposit withdrawal manager. func NewManager(cfg *ManagerConfig) *Manager { return &Manager{ - cfg: cfg, - initChan: make(chan struct{}), - activeWithdrawals: make(map[string][]*deposit.Deposit), + cfg: cfg, + initChan: make(chan struct{}), + activeWithdrawals: make(map[string][]*deposit.Deposit), + finalizedWithdrawalTxns: make(map[chainhash.Hash]*wire.MsgTx), } } @@ -187,12 +193,21 @@ func (m *Manager) recover() error { return err } - err = m.publishWithdrawal(m.runCtx, deposits, withdrawalAddress) + finalizedTx, err := m.createFinalizedWithdrawalTx( + m.runCtx, deposits, withdrawalAddress, + ) + if err != nil { + return err + } + + err = m.publishFinalizedWithdrawalTx(finalizedTx) if err != nil { return err } - err = m.handleWithdrawal(deposits, withdrawalAddress) + err = m.handleWithdrawal( + deposits, finalizedTx.TxHash(), withdrawalAddress, + ) if err != nil { return err } @@ -261,12 +276,21 @@ func (m *Manager) WithdrawDeposits(ctx context.Context, return err } - err = m.publishWithdrawal(ctx, deposits, withdrawalAddress) + finalizedTx, err := m.createFinalizedWithdrawalTx( + ctx, deposits, withdrawalAddress, + ) + if err != nil { + return err + } + + err = m.publishFinalizedWithdrawalTx(finalizedTx) if err != nil { return err } - err = m.handleWithdrawal(deposits, withdrawalAddress) + err = m.handleWithdrawal( + deposits, finalizedTx.TxHash(), withdrawalAddress, + ) if err != nil { return err } @@ -278,15 +302,16 @@ func (m *Manager) WithdrawDeposits(ctx context.Context, return nil } -func (m *Manager) publishWithdrawal(ctx context.Context, - deposits []*deposit.Deposit, withdrawalAddress btcutil.Address) error { +func (m *Manager) createFinalizedWithdrawalTx(ctx context.Context, + deposits []*deposit.Deposit, withdrawalAddress btcutil.Address) ( + *wire.MsgTx, error) { // Create a musig2 session for each deposit. withdrawalSessions, clientNonces, err := m.createMusig2Sessions( ctx, deposits, ) if err != nil { - return err + return nil, err } // Get the fee rate for the withdrawal sweep. @@ -294,7 +319,7 @@ func (m *Manager) publishWithdrawal(ctx context.Context, m.runCtx, defaultConfTarget, ) if err != nil { - return err + return nil, err } outpoints := toOutpoints(deposits) @@ -307,14 +332,14 @@ func (m *Manager) publishWithdrawal(ctx context.Context, }, ) if err != nil { - return err + return nil, err } addressParams, err := m.cfg.AddressManager.GetStaticAddressParameters( ctx, ) if err != nil { - return fmt.Errorf("couldn't get confirmation height for "+ + return nil, fmt.Errorf("couldn't get confirmation height for "+ "deposit, %v", err) } @@ -325,12 +350,12 @@ func (m *Manager) publishWithdrawal(ctx context.Context, withdrawalSweepFeeRate, ) if err != nil { - return err + return nil, err } coopServerNonces, err := toNonces(resp.ServerNonces) if err != nil { - return err + return nil, err } // Next we'll get our sweep tx signatures. @@ -340,25 +365,33 @@ func (m *Manager) publishWithdrawal(ctx context.Context, withdrawalSessions, coopServerNonces, ) if err != nil { - return err + return nil, err } // Now we'll finalize the sweepless sweep transaction. - finalizedWithdrawalTx, err := m.finalizeMusig2Transaction( + finalizedTx, err := m.finalizeMusig2Transaction( m.runCtx, outpoints, m.cfg.Signer, withdrawalSessions, withdrawalTx, resp.Musig2SweepSigs, ) if err != nil { - return err + return nil, err } - txLabel := fmt.Sprintf("deposit-withdrawal-%v", - finalizedWithdrawalTx.TxHash()) + m.finalizedWithdrawalTxns[finalizedTx.TxHash()] = finalizedTx + + return finalizedTx, nil +} + +func (m *Manager) publishFinalizedWithdrawalTx(tx *wire.MsgTx) error { + if tx == nil { + return errors.New("can't publish, finalized withdrawal tx is " + + "nil") + } + + txLabel := fmt.Sprintf("deposit-withdrawal-%v", tx.TxHash()) // Publish the withdrawal sweep transaction. - err = m.cfg.WalletKit.PublishTransaction( - m.runCtx, finalizedWithdrawalTx, txLabel, - ) + err := m.cfg.WalletKit.PublishTransaction(m.runCtx, tx, txLabel) if err != nil { if !strings.Contains(err.Error(), "output already spent") { @@ -366,14 +399,13 @@ func (m *Manager) publishWithdrawal(ctx context.Context, } } - log.Debugf("published deposit withdrawal with txid: %v", - finalizedWithdrawalTx.TxHash()) + log.Debugf("published deposit withdrawal with txid: %v", tx.TxHash()) return nil } func (m *Manager) handleWithdrawal(deposits []*deposit.Deposit, - withdrawalAddress btcutil.Address) error { + txHash chainhash.Hash, withdrawalAddress btcutil.Address) error { withdrawalPkScript, err := txscript.PayToAddrScript(withdrawalAddress) if err != nil { @@ -382,7 +414,7 @@ func (m *Manager) handleWithdrawal(deposits []*deposit.Deposit, m.Lock() confChan, errChan, err := m.cfg.ChainNotifier.RegisterConfirmationsNtfn( - m.runCtx, nil, withdrawalPkScript, MinConfs, + m.runCtx, &txHash, withdrawalPkScript, MinConfs, int32(m.initiationHeight), ) m.Unlock() @@ -402,9 +434,12 @@ func (m *Manager) handleWithdrawal(deposits []*deposit.Deposit, err) } - // Remove the withdrawal from the active withdrawals. + // Remove the withdrawal from the active withdrawals and + // remove its finalized to stop republishing it on block + // arrivals. m.Lock() delete(m.activeWithdrawals, withdrawalAddress.String()) + delete(m.finalizedWithdrawalTxns, txHash) m.Unlock() case err := <-errChan: @@ -710,17 +745,13 @@ func (m *Manager) toPrevOuts(deposits []*deposit.Deposit, } func (m *Manager) republishWithdrawals() error { - for withdrawalAddress, deposits := range m.activeWithdrawals { - address, err := btcutil.DecodeAddress( - withdrawalAddress, m.cfg.ChainParams, - ) - if err != nil { - log.Errorf("Error decoding withdrawal address: %v", err) - - return err + for _, finalizedTx := range m.finalizedWithdrawalTxns { + if finalizedTx == nil { + log.Warnf("Finalized withdrawal tx is nil") + continue } - err = m.publishWithdrawal(m.runCtx, deposits, address) + err := m.publishFinalizedWithdrawalTx(finalizedTx) if err != nil { log.Errorf("Error republishing withdrawal: %v", err)