From 93e8428b6bc20c5730008804089e5841f2baf537 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 8 Aug 2023 19:18:34 -0700 Subject: [PATCH] lnwallet: for taproot channels allow the co-op close tx to be RBF'd This is a preparatory change for the upcoming "simple channel close" feature which'll utilize RBF to allow either side to resign the co-op close transaction for broadcast at any point. --- lnwallet/channel.go | 62 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index f283ed837..412df4cfe 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -17,6 +17,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/mempool" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog" @@ -3074,6 +3075,9 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool, return c, nil } +// fundingTxIn returns the funding output as a transaction input. The input +// returned by this function uses a max sequence number, so it isn't able to be +// used with RBF by default. func fundingTxIn(chanState *channeldb.OpenChannel) wire.TxIn { return *wire.NewTxIn(&chanState.FundingOutpoint, nil, nil) } @@ -7530,10 +7534,17 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, return nil, nil, 0, err } + var closeTxOpts []CloseTxOpt + + // If this is a taproot channel, then we use an RBF'able funding input. + if lc.channelState.ChanType.IsTaproot() { + closeTxOpts = append(closeTxOpts, WithRBFCloseTx()) + } + closeTx := CreateCooperativeCloseTx( fundingTxIn(lc.channelState), lc.channelState.LocalChanCfg.DustLimit, lc.channelState.RemoteChanCfg.DustLimit, ourBalance, theirBalance, - localDeliveryScript, remoteDeliveryScript, + localDeliveryScript, remoteDeliveryScript, closeTxOpts..., ) // Ensure that the transaction doesn't explicitly violate any @@ -7609,13 +7620,20 @@ func (lc *LightningChannel) CompleteCooperativeClose( return nil, 0, err } + var closeTxOpts []CloseTxOpt + + // If this is a taproot channel, then we use an RBF'able funding input. + if lc.channelState.ChanType.IsTaproot() { + closeTxOpts = append(closeTxOpts, WithRBFCloseTx()) + } + // Create the transaction used to return the current settled balance // on this active channel back to both parties. In this current model, // the initiator pays full fees for the cooperative close transaction. closeTx := CreateCooperativeCloseTx( fundingTxIn(lc.channelState), lc.channelState.LocalChanCfg.DustLimit, lc.channelState.RemoteChanCfg.DustLimit, ourBalance, theirBalance, - localDeliveryScript, remoteDeliveryScript, + localDeliveryScript, remoteDeliveryScript, closeTxOpts..., ) // Ensure that the transaction doesn't explicitly validate any @@ -8229,6 +8247,32 @@ func (lc *LightningChannel) generateRevocation(height uint64) (*lnwire.RevokeAnd return revocationMsg, nil } +// closeTxOpts houses the set of options that modify how the cooperative close +// tx is to be constructed. +type closeTxOpts struct { + // enableRBF indicates whether the cooperative close tx should signal + // RBF or not. + enableRBF bool +} + +// defaultCloseTxOpts returns a closeTxOpts struct with default values. +func defaultCloseTxOpts() closeTxOpts { + return closeTxOpts{ + enableRBF: false, + } +} + +// CloseTxOpt is a functional option that allows us to modify how the closing +// transaction is created. +type CloseTxOpt func(*closeTxOpts) + +// WithRBFCloseTx signals that the cooperative close tx should signal RBF. +func WithRBFCloseTx() CloseTxOpt { + return func(o *closeTxOpts) { + o.enableRBF = true + } +} + // CreateCooperativeCloseTx creates a transaction which if signed by both // parties, then broadcast cooperatively closes an active channel. The creation // of the closure transaction is modified by a boolean indicating if the party @@ -8237,7 +8281,19 @@ func (lc *LightningChannel) generateRevocation(height uint64) (*lnwire.RevokeAnd // transaction in full. func CreateCooperativeCloseTx(fundingTxIn wire.TxIn, localDust, remoteDust, ourBalance, theirBalance btcutil.Amount, - ourDeliveryScript, theirDeliveryScript []byte) *wire.MsgTx { + ourDeliveryScript, theirDeliveryScript []byte, + closeOpts ...CloseTxOpt) *wire.MsgTx { + + opts := defaultCloseTxOpts() + for _, optFunc := range closeOpts { + optFunc(&opts) + } + + // If RBF is signalled, then we'll modify the sequence to permit + // replacement. + if opts.enableRBF { + fundingTxIn.Sequence = mempool.MaxRBFSequence + } // Construct the transaction to perform a cooperative closure of the // channel. In the event that one side doesn't have any settled funds