Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions contractcourt/chain_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,76 @@ func (c *chainWatcher) toSelfAmount(tx *wire.MsgTx) btcutil.Amount {
return btcutil.Amount(fn.Sum(vals))
}

// finalizeCoopClose calls the aux closer to finalize a cooperative close
// transaction that has been confirmed on-chain.
func (c *chainWatcher) finalizeCoopClose(aux AuxChanCloser,
closeTx *wire.MsgTx) error {

chanState := c.cfg.chanState

// Get the shutdown info to extract the local delivery script.
shutdown, err := chanState.ShutdownInfo()
if err != nil {
return fmt.Errorf("get shutdown info: %w", err)
}

// TODO(roasbeef): Extract the internal key if this is a taproot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a todo specifically for roasbeef and not for @GeorgeTsagk?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This todo was meant for @GeorgeTsagk , but realized now it is not needed at all. Will update comments accordingly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

// channel. For now, we leave it as None since we don't persist it
// separately from the delivery script.
var internalKey fn.Option[btcec.PublicKey]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the existing call site, this is always properly set.


// Build the AuxShutdownReq.
req := AuxShutdownReq{
ChanPoint: chanState.FundingOutpoint,
ShortChanID: chanState.ShortChanID(),
InternalKey: internalKey,
Initiator: chanState.IsInitiator,
CommitBlob: chanState.LocalCommitment.CustomBlob,
FundingBlob: chanState.CustomBlob,
}

// Extract close outputs from the transaction. We need to identify
// which outputs belong to local vs remote parties.
var localCloseOutput, remoteCloseOutput fn.Option[CloseOutput]

// Get the delivery scripts for both parties.
var localDeliveryScript lnwire.DeliveryAddress
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says both parties, but lonly extracts this script for a single party.

shutdown.WhenSome(func(s channeldb.ShutdownInfo) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC this must be set at this point. We can encode this invariant by returning an error if it isn't available.

localDeliveryScript = s.DeliveryScript.Val
})

// Scan through the close transaction outputs to identify local and
// remote outputs.
for _, out := range closeTx.TxOut {
if len(localDeliveryScript) > 0 &&
slices.Equal(out.PkScript, localDeliveryScript) {

localCloseOutput = fn.Some(CloseOutput{
Amt: btcutil.Amount(out.Value),
PkScript: out.PkScript,
DustLimit: chanState.LocalChanCfg.DustLimit,
})
} else {
// This must be the remote output.
remoteCloseOutput = fn.Some(CloseOutput{
Amt: btcutil.Amount(out.Value),
PkScript: out.PkScript,
DustLimit: chanState.RemoteChanCfg.DustLimit,
})
}
}

// Build the AuxCloseDesc.
desc := AuxCloseDesc{
AuxShutdownReq: req,
LocalCloseOutput: localCloseOutput,
RemoteCloseOutput: remoteCloseOutput,
}

// Call FinalizeClose on the aux closer.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superflous comments here and above.

return aux.FinalizeClose(desc, closeTx)
}

// dispatchCooperativeClose processed a detect cooperative channel closure.
// We'll use the spending transaction to locate our output within the
// transaction, then clean up the database state. We'll also dispatch a
Expand Down Expand Up @@ -1111,6 +1181,17 @@ func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDet
ChannelCloseSummary: closeSummary,
}

// If we have an aux closer, finalize the cooperative close now that
// it's confirmed.
err = fn.MapOptionZ(
c.cfg.auxCloser, func(aux AuxChanCloser) error {
return c.finalizeCoopClose(aux, broadcastTx)
},
)
if err != nil {
return fmt.Errorf("finalize coop close: %w", err)
}

// With the event processed, we'll now notify all subscribers of the
// event.
c.Lock()
Expand Down
27 changes: 0 additions & 27 deletions lnwallet/chancloser/chancloser.go
Original file line number Diff line number Diff line change
Expand Up @@ -970,33 +970,6 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
}
c.closingTx = closeTx

// If there's an aux chan closer, then we'll finalize with it
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: is there value in leaving this code?

This PR introduces more robust handling of the aux closer finalization by moving the hook call site from ReceiveClosingSigned to the chainwatcher. That also introduces a waiting time of ~10 minutes before the finalization is initiated. Couldn't we keep doing both?

In case our peer stays online this would lead to a faster finalization which in turn maybe is a better user experience.

// before we write to disk.
err = fn.MapOptionZ(
c.cfg.AuxCloser, func(aux AuxChanCloser) error {
channel := c.cfg.Channel
//nolint:ll
req := AuxShutdownReq{
ChanPoint: c.chanPoint,
ShortChanID: c.cfg.Channel.ShortChanID(),
InternalKey: c.localInternalKey,
Initiator: channel.IsInitiator(),
CommitBlob: channel.LocalCommitmentBlob(),
FundingBlob: channel.FundingBlob(),
}
desc := AuxCloseDesc{
AuxShutdownReq: req,
LocalCloseOutput: c.localCloseOutput,
RemoteCloseOutput: c.remoteCloseOutput,
}

return aux.FinalizeClose(desc, closeTx)
},
)
if err != nil {
return noClosing, err
}

// Before publishing the closing tx, we persist it to the
// database, such that it can be republished if something goes
// wrong.
Expand Down