@@ -7691,10 +7691,21 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
76917691 }, nil
76927692}
76937693
7694+ // CloseOutput wraps a normal tx out with additional metadata that indicates if
7695+ // the output belongs to the initiator of the channel or not.
7696+ type CloseOutput struct {
7697+ wire.TxOut
7698+
7699+ // IsLocal indicates if the output belong to the local party.
7700+ IsLocal bool
7701+ }
7702+
76947703// chanCloseOpt is a functional option that can be used to modify the co-op
76957704// close process.
76967705type chanCloseOpt struct {
76977706 musigSession * MusigSession
7707+
7708+ extraCloseOutputs []CloseOutput
76987709}
76997710
77007711// ChanCloseOpt is a closure type that cen be used to modify the set of default
@@ -7715,16 +7726,21 @@ func WithCoopCloseMusigSession(session *MusigSession) ChanCloseOpt {
77157726 }
77167727}
77177728
7729+ // WithExtraCloseOutputs can be used to add extra outputs to the cooperative
7730+ // transaction.
7731+ func WithExtraCloseOutputs (extraOutputs []CloseOutput ) ChanCloseOpt {
7732+ return func (opts * chanCloseOpt ) {
7733+ opts .extraCloseOutputs = extraOutputs
7734+ }
7735+ }
7736+
77187737// CreateCloseProposal is used by both parties in a cooperative channel close
77197738// workflow to generate proposed close transactions and signatures. This method
77207739// should only be executed once all pending HTLCs (if any) on the channel have
77217740// been cleared/removed. Upon completion, the source channel will shift into
77227741// the "closing" state, which indicates that all incoming/outgoing HTLC
77237742// requests should be rejected. A signature for the closing transaction is
77247743// returned.
7725- //
7726- // TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs,
7727- // settle any in flight.
77287744func (lc * LightningChannel ) CreateCloseProposal (proposedFee btcutil.Amount ,
77297745 localDeliveryScript []byte , remoteDeliveryScript []byte ,
77307746 closeOpts ... ChanCloseOpt ) (input.Signature , * chainhash.Hash ,
@@ -7735,7 +7751,6 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount,
77357751
77367752 // If we're already closing the channel, then ignore this request.
77377753 if lc .isClosed {
7738- // TODO(roasbeef): check to ensure no pending payments
77397754 return nil , nil , 0 , ErrChanClosing
77407755 }
77417756
@@ -7762,6 +7777,14 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount,
77627777 closeTxOpts = append (closeTxOpts , WithRBFCloseTx ())
77637778 }
77647779
7780+ // If we have any extra outputs to pass along, then we'll map that to
7781+ // the co-op close option txn type.
7782+ if opts .extraCloseOutputs != nil {
7783+ closeTxOpts = append (closeTxOpts , WithExtraTxCloseOutputs (
7784+ opts .extraCloseOutputs ,
7785+ ))
7786+ }
7787+
77657788 closeTx := CreateCooperativeCloseTx (
77667789 fundingTxIn (lc .channelState ), lc .channelState .LocalChanCfg .DustLimit ,
77677790 lc .channelState .RemoteChanCfg .DustLimit , ourBalance , theirBalance ,
@@ -7844,6 +7867,14 @@ func (lc *LightningChannel) CompleteCooperativeClose(
78447867 closeTxOpts = append (closeTxOpts , WithRBFCloseTx ())
78457868 }
78467869
7870+ // If we have any extra outputs to pass along, then we'll map that to
7871+ // the co-op close option txn type.
7872+ if opts .extraCloseOutputs != nil {
7873+ closeTxOpts = append (closeTxOpts , WithExtraTxCloseOutputs (
7874+ opts .extraCloseOutputs ,
7875+ ))
7876+ }
7877+
78477878 // Create the transaction used to return the current settled balance
78487879 // on this active channel back to both parties. In this current model,
78497880 // the initiator pays full fees for the cooperative close transaction.
@@ -8549,6 +8580,10 @@ type closeTxOpts struct {
85498580 // enableRBF indicates whether the cooperative close tx should signal
85508581 // RBF or not.
85518582 enableRBF bool
8583+
8584+ // extraCloseOutputs is a set of additional outputs that should be
8585+ // added the co-op close transaction.
8586+ extraCloseOutputs []CloseOutput
85528587}
85538588
85548589// defaultCloseTxOpts returns a closeTxOpts struct with default values.
@@ -8569,6 +8604,14 @@ func WithRBFCloseTx() CloseTxOpt {
85698604 }
85708605}
85718606
8607+ // WithExtraTxCloseOutputs can be used to add extra outputs to the cooperative
8608+ // transaction.
8609+ func WithExtraTxCloseOutputs (extraOutputs []CloseOutput ) CloseTxOpt {
8610+ return func (o * closeTxOpts ) {
8611+ o .extraCloseOutputs = extraOutputs
8612+ }
8613+ }
8614+
85728615// CreateCooperativeCloseTx creates a transaction which if signed by both
85738616// parties, then broadcast cooperatively closes an active channel. The creation
85748617// of the closure transaction is modified by a boolean indicating if the party
@@ -8600,17 +8643,115 @@ func CreateCooperativeCloseTx(fundingTxIn wire.TxIn,
86008643
86018644 // Create both cooperative closure outputs, properly respecting the
86028645 // dust limits of both parties.
8603- if ourBalance >= localDust {
8646+ var localOutputIdx fn.Option [int ]
8647+ haveLocalOutput := ourBalance >= localDust
8648+ if haveLocalOutput {
86048649 closeTx .AddTxOut (& wire.TxOut {
86058650 PkScript : ourDeliveryScript ,
86068651 Value : int64 (ourBalance ),
86078652 })
8653+
8654+ localOutputIdx = fn .Some (len (closeTx .TxOut ) - 1 )
86088655 }
8609- if theirBalance >= remoteDust {
8656+
8657+ var remoteOutputIdx fn.Option [int ]
8658+ haveRemoteOutput := theirBalance >= remoteDust
8659+ if haveRemoteOutput {
86108660 closeTx .AddTxOut (& wire.TxOut {
86118661 PkScript : theirDeliveryScript ,
86128662 Value : int64 (theirBalance ),
86138663 })
8664+
8665+ remoteOutputIdx = fn .Some (len (closeTx .TxOut ) - 1 )
8666+ }
8667+
8668+ // If we have extra outputs to add to the co-op close transaction, then
8669+ // we'll examine them now. We'll deduct the output's value from the
8670+ // owning party. In the case that a party can't pay for the output, then
8671+ // their normal output will be omitted.
8672+ for _ , extraTxOut := range opts .extraCloseOutputs {
8673+ switch {
8674+ // For additional local outputs, add the output, then deduct
8675+ // the balance from our local balance.
8676+ case extraTxOut .IsLocal :
8677+ // The extraCloseOutputs in the options just indicate if
8678+ // an extra output should be added in general. But we
8679+ // only add one if we actually _need_ one, based on the
8680+ // balance. If we don't have enough local balance to
8681+ // cover the extra output, then localOutputIdx is None.
8682+ localOutputIdx .WhenSome (func (idx int ) {
8683+ // The output that currently represents the
8684+ // local balance, which means:
8685+ // txOut.Value == ourBalance.
8686+ txOut := closeTx .TxOut [idx ]
8687+
8688+ // The extra output (if one exists) is the more
8689+ // important one, as in custom channels it might
8690+ // carry some additional values. The normal
8691+ // output is just an address that sends the
8692+ // local balance back to our wallet. The extra
8693+ // one also goes to our wallet, but might also
8694+ // carry other values, so it has higher
8695+ // priority. Do we have enough balance to have
8696+ // both the extra output with the given value
8697+ // (which is subtracted from our balance) and
8698+ // still an above-dust normal output? If not, we
8699+ // skip the extra output and just overwrite the
8700+ // existing output script with the one from the
8701+ // extra output.
8702+ amtAfterOutput := btcutil .Amount (
8703+ txOut .Value - extraTxOut .Value ,
8704+ )
8705+ if amtAfterOutput <= localDust {
8706+ txOut .PkScript = extraTxOut .PkScript
8707+
8708+ return
8709+ }
8710+
8711+ txOut .Value -= extraTxOut .Value
8712+ closeTx .AddTxOut (& extraTxOut .TxOut )
8713+ })
8714+
8715+ // For extra remote outputs, we'll do the opposite.
8716+ case ! extraTxOut .IsLocal :
8717+ // The extraCloseOutputs in the options just indicate if
8718+ // an extra output should be added in general. But we
8719+ // only add one if we actually _need_ one, based on the
8720+ // balance. If we don't have enough remote balance to
8721+ // cover the extra output, then remoteOutputIdx is None.
8722+ remoteOutputIdx .WhenSome (func (idx int ) {
8723+ // The output that currently represents the
8724+ // remote balance, which means:
8725+ // txOut.Value == theirBalance.
8726+ txOut := closeTx .TxOut [idx ]
8727+
8728+ // The extra output (if one exists) is the more
8729+ // important one, as in custom channels it might
8730+ // carry some additional values. The normal
8731+ // output is just an address that sends the
8732+ // remote balance back to their wallet. The
8733+ // extra one also goes to their wallet, but
8734+ // might also carry other values, so it has
8735+ // higher priority. Do they have enough balance
8736+ // to have both the extra output with the given
8737+ // value (which is subtracted from their
8738+ // balance) and still an above-dust normal
8739+ // output? If not, we skip the extra output and
8740+ // just overwrite the existing output script
8741+ // with the one from the extra output.
8742+ amtAfterOutput := btcutil .Amount (
8743+ txOut .Value - extraTxOut .Value ,
8744+ )
8745+ if amtAfterOutput <= remoteDust {
8746+ txOut .PkScript = extraTxOut .PkScript
8747+
8748+ return
8749+ }
8750+
8751+ txOut .Value -= extraTxOut .Value
8752+ closeTx .AddTxOut (& extraTxOut .TxOut )
8753+ })
8754+ }
86148755 }
86158756
86168757 txsort .InPlaceSort (closeTx )
0 commit comments