Skip to content

Commit 8d651b9

Browse files
Roasbeefguggero
authored andcommitted
lnwallet/chancloser: add aux chan closer, use in coop flow
1 parent 7ff251c commit 8d651b9

File tree

4 files changed

+269
-17
lines changed

4 files changed

+269
-17
lines changed

lnwallet/chancloser/chancloser.go

Lines changed: 185 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ type ChanCloseCfg struct {
140140
// FeeEstimator is used to estimate the absolute starting co-op close
141141
// fee.
142142
FeeEstimator CoopFeeEstimator
143+
144+
// AuxCloser is an optional interface that can be used to modify the
145+
// way the co-op close process proceeds.
146+
AuxCloser fn.Option[AuxChanCloser]
143147
}
144148

145149
// ChanCloser is a state machine that handles the cooperative channel closure
@@ -215,6 +219,20 @@ type ChanCloser struct {
215219
// we use to handle a specific race condition caused by the independent
216220
// message processing queues.
217221
cachedClosingSigned fn.Option[lnwire.ClosingSigned]
222+
223+
// localCloseOutput is the local output on the closing transaction that
224+
// the local party should be paid to. This will only be populated if the
225+
// local balance isn't dust.
226+
localCloseOutput fn.Option[CloseOutput]
227+
228+
// remoteCloseOutput is the remote output on the closing transaction
229+
// that the remote party should be paid to. This will only be populated
230+
// if the remote balance isn't dust.
231+
remoteCloseOutput fn.Option[CloseOutput]
232+
233+
// auxOutputs are the optional additional outputs that might be added to
234+
// the closing transaction.
235+
auxOutputs fn.Option[AuxCloseOutputs]
218236
}
219237

220238
// calcCoopCloseFee computes an "ideal" absolute co-op close fee given the
@@ -295,13 +313,13 @@ func (c *ChanCloser) initFeeBaseline() {
295313
// Depending on if a balance ends up being dust or not, we'll pass a
296314
// nil TxOut into the EstimateFee call which can handle it.
297315
var localTxOut, remoteTxOut *wire.TxOut
298-
if !c.cfg.Channel.LocalBalanceDust() {
316+
if isDust, _ := c.cfg.Channel.LocalBalanceDust(); !isDust {
299317
localTxOut = &wire.TxOut{
300318
PkScript: c.localDeliveryScript,
301319
Value: 0,
302320
}
303321
}
304-
if !c.cfg.Channel.RemoteBalanceDust() {
322+
if isDust, _ := c.cfg.Channel.RemoteBalanceDust(); !isDust {
305323
remoteTxOut = &wire.TxOut{
306324
PkScript: c.remoteDeliveryScript,
307325
Value: 0,
@@ -337,6 +355,30 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
337355
// desired closing script.
338356
shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript)
339357

358+
// At this point, we'll check to see if we have any custom records to
359+
// add to the shutdown message.
360+
err := fn.MapOptionZ(c.cfg.AuxCloser, func(a AuxChanCloser) error {
361+
shutdownCustomRecords, err := a.ShutdownBlob(AuxShutdownReq{
362+
ChanPoint: c.chanPoint,
363+
ShortChanID: c.cfg.Channel.ShortChanID(),
364+
Initiator: c.cfg.Channel.IsInitiator(),
365+
CommitBlob: c.cfg.Channel.LocalCommitmentBlob(),
366+
FundingBlob: c.cfg.Channel.FundingBlob(),
367+
})
368+
if err != nil {
369+
return err
370+
}
371+
372+
shutdownCustomRecords.WhenSome(func(cr lnwire.CustomRecords) {
373+
shutdown.CustomRecords = cr
374+
})
375+
376+
return nil
377+
})
378+
if err != nil {
379+
return nil, err
380+
}
381+
340382
// If this is a taproot channel, then we'll need to also generate a
341383
// nonce that'll be used sign the co-op close transaction offer.
342384
if c.cfg.Channel.ChanType().IsTaproot() {
@@ -370,11 +412,22 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
370412
shutdownInfo := channeldb.NewShutdownInfo(
371413
c.localDeliveryScript, c.closer.IsLocal(),
372414
)
373-
err := c.cfg.Channel.MarkShutdownSent(shutdownInfo)
415+
err = c.cfg.Channel.MarkShutdownSent(shutdownInfo)
374416
if err != nil {
375417
return nil, err
376418
}
377419

420+
// We'll track our local close output, even if it's dust in BTC terms,
421+
// it might still carry value in custom channel terms.
422+
_, dustAmt := c.cfg.Channel.LocalBalanceDust()
423+
localBalance, _ := c.cfg.Channel.CommitBalances()
424+
c.localCloseOutput = fn.Some(CloseOutput{
425+
Amt: localBalance,
426+
DustLimit: dustAmt,
427+
PkScript: c.localDeliveryScript,
428+
ShutdownRecords: shutdown.CustomRecords,
429+
})
430+
378431
return shutdown, nil
379432
}
380433

@@ -444,6 +497,21 @@ func (c *ChanCloser) NegotiationHeight() uint32 {
444497
return c.negotiationHeight
445498
}
446499

500+
// LocalCloseOutput returns the local close output.
501+
func (c *ChanCloser) LocalCloseOutput() fn.Option[CloseOutput] {
502+
return c.localCloseOutput
503+
}
504+
505+
// RemoteCloseOutput returns the remote close output.
506+
func (c *ChanCloser) RemoteCloseOutput() fn.Option[CloseOutput] {
507+
return c.remoteCloseOutput
508+
}
509+
510+
// AuxOutputs returns optional extra outputs.
511+
func (c *ChanCloser) AuxOutputs() fn.Option[AuxCloseOutputs] {
512+
return c.auxOutputs
513+
}
514+
447515
// validateShutdownScript attempts to match and validate the script provided in
448516
// our peer's shutdown message with the upfront shutdown script we have on
449517
// record. For any script specified, we also make sure it matches our
@@ -503,6 +571,17 @@ func (c *ChanCloser) ReceiveShutdown(msg lnwire.Shutdown) (
503571

504572
noShutdown := fn.None[lnwire.Shutdown]()
505573

574+
// We'll track their remote close output, even if it's dust in BTC
575+
// terms, it might still carry value in custom channel terms.
576+
_, dustAmt := c.cfg.Channel.RemoteBalanceDust()
577+
_, remoteBalance := c.cfg.Channel.CommitBalances()
578+
c.remoteCloseOutput = fn.Some(CloseOutput{
579+
Amt: remoteBalance,
580+
DustLimit: dustAmt,
581+
PkScript: msg.Address,
582+
ShutdownRecords: msg.CustomRecords,
583+
})
584+
506585
switch c.state {
507586
// If we're in the close idle state, and we're receiving a channel
508587
// closure related message, then this indicates that we're on the
@@ -850,6 +929,25 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
850929
}
851930
}
852931

932+
// Before we complete the cooperative close, we'll see if we
933+
// have any extra aux options.
934+
c.auxOutputs, err = c.auxCloseOutputs(remoteProposedFee)
935+
if err != nil {
936+
return noClosing, err
937+
}
938+
c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) {
939+
closeOpts = append(
940+
closeOpts, lnwallet.WithExtraCloseOutputs(
941+
outs.ExtraCloseOutputs,
942+
),
943+
)
944+
closeOpts = append(
945+
closeOpts, lnwallet.WithCustomCoopSort(
946+
outs.CustomSort,
947+
),
948+
)
949+
})
950+
853951
closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose(
854952
localSig, remoteSig, c.localDeliveryScript,
855953
c.remoteDeliveryScript, remoteProposedFee, closeOpts...,
@@ -859,6 +957,32 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
859957
}
860958
c.closingTx = closeTx
861959

960+
// If there's an aux chan closer, then we'll finalize with it
961+
// before we write to disk.
962+
err = fn.MapOptionZ(
963+
c.cfg.AuxCloser, func(aux AuxChanCloser) error {
964+
channel := c.cfg.Channel
965+
//nolint:lll
966+
req := AuxShutdownReq{
967+
ChanPoint: c.chanPoint,
968+
ShortChanID: c.cfg.Channel.ShortChanID(),
969+
Initiator: channel.IsInitiator(),
970+
CommitBlob: channel.LocalCommitmentBlob(),
971+
FundingBlob: channel.FundingBlob(),
972+
}
973+
desc := AuxCloseDesc{
974+
AuxShutdownReq: req,
975+
LocalCloseOutput: c.localCloseOutput,
976+
RemoteCloseOutput: c.remoteCloseOutput,
977+
}
978+
979+
return aux.FinalizeClose(desc, closeTx)
980+
},
981+
)
982+
if err != nil {
983+
return noClosing, err
984+
}
985+
862986
// Before publishing the closing tx, we persist it to the
863987
// database, such that it can be republished if something goes
864988
// wrong.
@@ -908,9 +1032,45 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
9081032
}
9091033
}
9101034

1035+
// auxCloseOutputs returns any additional outputs that should be used when
1036+
// closing the channel.
1037+
func (c *ChanCloser) auxCloseOutputs(
1038+
closeFee btcutil.Amount) (fn.Option[AuxCloseOutputs], error) {
1039+
1040+
var closeOuts fn.Option[AuxCloseOutputs]
1041+
err := fn.MapOptionZ(c.cfg.AuxCloser, func(aux AuxChanCloser) error {
1042+
req := AuxShutdownReq{
1043+
ChanPoint: c.chanPoint,
1044+
ShortChanID: c.cfg.Channel.ShortChanID(),
1045+
Initiator: c.cfg.Channel.IsInitiator(),
1046+
CommitBlob: c.cfg.Channel.LocalCommitmentBlob(),
1047+
FundingBlob: c.cfg.Channel.FundingBlob(),
1048+
}
1049+
outs, err := aux.AuxCloseOutputs(AuxCloseDesc{
1050+
AuxShutdownReq: req,
1051+
CloseFee: closeFee,
1052+
CommitFee: c.cfg.Channel.CommitFee(),
1053+
LocalCloseOutput: c.localCloseOutput,
1054+
RemoteCloseOutput: c.remoteCloseOutput,
1055+
})
1056+
if err != nil {
1057+
return err
1058+
}
1059+
1060+
closeOuts = outs
1061+
1062+
return nil
1063+
})
1064+
if err != nil {
1065+
return closeOuts, err
1066+
}
1067+
1068+
return closeOuts, nil
1069+
}
1070+
9111071
// proposeCloseSigned attempts to propose a new signature for the closing
912-
// transaction for a channel based on the prior fee negotiations and our current
913-
// compromise fee.
1072+
// transaction for a channel based on the prior fee negotiations and our
1073+
// current compromise fee.
9141074
func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (
9151075
*lnwire.ClosingSigned, error) {
9161076

@@ -928,6 +1088,26 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (
9281088
}
9291089
}
9301090

1091+
// We'll also now see if the aux chan closer has any additional options
1092+
// for the closing purpose.
1093+
c.auxOutputs, err = c.auxCloseOutputs(fee)
1094+
if err != nil {
1095+
return nil, err
1096+
}
1097+
c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) {
1098+
closeOpts = append(
1099+
closeOpts, lnwallet.WithExtraCloseOutputs(
1100+
outs.ExtraCloseOutputs,
1101+
),
1102+
)
1103+
closeOpts = append(
1104+
closeOpts, lnwallet.WithCustomCoopSort(
1105+
outs.CustomSort,
1106+
),
1107+
)
1108+
})
1109+
1110+
// With all our options added, we'll attempt to co-op close now.
9311111
rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal(
9321112
fee, c.localDeliveryScript, c.remoteDeliveryScript,
9331113
closeOpts...,

lnwallet/chancloser/chancloser_test.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/lightningnetwork/lnd/lnwallet"
2323
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
2424
"github.com/lightningnetwork/lnd/lnwire"
25+
"github.com/lightningnetwork/lnd/tlv"
2526
"github.com/stretchr/testify/require"
2627
)
2728

@@ -152,6 +153,14 @@ func (m *mockChannel) ChannelPoint() wire.OutPoint {
152153
return m.chanPoint
153154
}
154155

156+
func (m *mockChannel) LocalCommitmentBlob() fn.Option[tlv.Blob] {
157+
return fn.None[tlv.Blob]()
158+
}
159+
160+
func (m *mockChannel) FundingBlob() fn.Option[tlv.Blob] {
161+
return fn.None[tlv.Blob]()
162+
}
163+
155164
func (m *mockChannel) MarkCoopBroadcasted(*wire.MsgTx,
156165
lntypes.ChannelParty) error {
157166

@@ -205,12 +214,20 @@ func (m *mockChannel) CompleteCooperativeClose(localSig,
205214
return &wire.MsgTx{}, 0, nil
206215
}
207216

208-
func (m *mockChannel) LocalBalanceDust() bool {
209-
return false
217+
func (m *mockChannel) LocalBalanceDust() (bool, btcutil.Amount) {
218+
return false, 0
219+
}
220+
221+
func (m *mockChannel) RemoteBalanceDust() (bool, btcutil.Amount) {
222+
return false, 0
223+
}
224+
225+
func (m *mockChannel) CommitBalances() (btcutil.Amount, btcutil.Amount) {
226+
return 0, 0
210227
}
211228

212-
func (m *mockChannel) RemoteBalanceDust() bool {
213-
return false
229+
func (m *mockChannel) CommitFee() btcutil.Amount {
230+
return 0
214231
}
215232

216233
func (m *mockChannel) ChanType() channeldb.ChannelType {

lnwallet/chancloser/interface.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import (
66
"github.com/btcsuite/btcd/chaincfg/chainhash"
77
"github.com/btcsuite/btcd/wire"
88
"github.com/lightningnetwork/lnd/channeldb"
9+
"github.com/lightningnetwork/lnd/fn"
910
"github.com/lightningnetwork/lnd/input"
1011
"github.com/lightningnetwork/lnd/lntypes"
1112
"github.com/lightningnetwork/lnd/lnwallet"
1213
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
1314
"github.com/lightningnetwork/lnd/lnwire"
15+
"github.com/lightningnetwork/lnd/tlv"
1416
)
1517

1618
// CoopFeeEstimator is used to estimate the fee of a co-op close transaction.
@@ -32,6 +34,14 @@ type Channel interface { //nolint:interfacebloat
3234
// ChannelPoint returns the channel point of the target channel.
3335
ChannelPoint() wire.OutPoint
3436

37+
// LocalCommitmentBlob may return the auxiliary data storage blob for
38+
// the local commitment transaction.
39+
LocalCommitmentBlob() fn.Option[tlv.Blob]
40+
41+
// FundingBlob may return the auxiliary data storage blob related to
42+
// funding details for the channel.
43+
FundingBlob() fn.Option[tlv.Blob]
44+
3545
// MarkCoopBroadcasted persistently marks that the channel close
3646
// transaction has been broadcast.
3747
MarkCoopBroadcasted(*wire.MsgTx, lntypes.ChannelParty) error
@@ -60,13 +70,23 @@ type Channel interface { //nolint:interfacebloat
6070

6171
// LocalBalanceDust returns true if when creating a co-op close
6272
// transaction, the balance of the local party will be dust after
63-
// accounting for any anchor outputs.
64-
LocalBalanceDust() bool
73+
// accounting for any anchor outputs. The dust value for the local
74+
// party is also returned.
75+
LocalBalanceDust() (bool, btcutil.Amount)
6576

6677
// RemoteBalanceDust returns true if when creating a co-op close
6778
// transaction, the balance of the remote party will be dust after
68-
// accounting for any anchor outputs.
69-
RemoteBalanceDust() bool
79+
// accounting for any anchor outputs. The dust value the remote party
80+
// is also returned.
81+
RemoteBalanceDust() (bool, btcutil.Amount)
82+
83+
// CommitBalances returns the local and remote balances in the current
84+
// commitment state.
85+
CommitBalances() (btcutil.Amount, btcutil.Amount)
86+
87+
// CommitFee returns the commitment fee for the current commitment
88+
// state.
89+
CommitFee() btcutil.Amount
7090

7191
// RemoteUpfrontShutdownScript returns the upfront shutdown script of
7292
// the remote party. If the remote party didn't specify such a script,

0 commit comments

Comments
 (0)