Skip to content

Commit

Permalink
staticaddr: split withdrawal tx creation/publishing
Browse files Browse the repository at this point in the history
In this commit we separate the withdrawal tx creation
and publication in order to avoid the coop signing
process on each re-publishing.
  • Loading branch information
hieblmi committed Jul 22, 2024
1 parent ae3a983 commit 8dec47e
Showing 1 changed file with 68 additions and 37 deletions.
105 changes: 68 additions & 37 deletions staticaddr/withdraw/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -278,23 +302,24 @@ 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.
withdrawalSweepFeeRate, err := m.cfg.WalletKit.EstimateFeeRate(
m.runCtx, defaultConfTarget,
)
if err != nil {
return err
return nil, err
}

outpoints := toOutpoints(deposits)
Expand All @@ -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)
}

Expand All @@ -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.
Expand All @@ -340,40 +365,47 @@ 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") {
log.Errorf("%v: %v", txLabel, err)
}
}

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 {
Expand All @@ -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()
Expand All @@ -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:
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 8dec47e

Please sign in to comment.