Skip to content

Commit 517608c

Browse files
Roasbeefguggero
authored andcommitted
lnwallet: add ability to add extra co-op close outputs
1 parent 7b396f4 commit 517608c

File tree

2 files changed

+460
-6
lines changed

2 files changed

+460
-6
lines changed

lnwallet/channel.go

Lines changed: 147 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
76967705
type 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.
77287744
func (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

Comments
 (0)