chancloser: extract ProcessCloseMsg Shutdown handling logic into dedicated method

This commit is contained in:
Keagan McClelland 2023-12-14 18:14:03 -08:00
parent 30ad49a29d
commit 6fef9ea2d7

View File

@ -11,6 +11,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
@ -475,28 +476,22 @@ func validateShutdownScript(disconnect func() error, upfrontScript,
return nil
}
// ProcessCloseMsg attempts to process the next message in the closing series.
// This method will update the state accordingly and return two primary values:
// the next set of messages to be sent, and a bool indicating if the fee
// negotiation process has completed. If the second value is true, then this
// means the ChanCloser can be garbage collected.
func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
bool, error) {
// ReceiveShutdown takes a raw Shutdown message and uses it to try and advance
// the ChanCloser state machine, failing if it is coming in at an invalid time.
// If appropriate, it will also generate a Shutdown message of its own to send
// out to the peer. It is possible for this method to return None when no error
// occurred.
func (c *ChanCloser) ReceiveShutdown(
msg lnwire.Shutdown,
) (fn.Option[lnwire.Shutdown], error) {
noShutdown := fn.None[lnwire.Shutdown]()
switch c.state {
// If we're in the close idle state, and we're receiving a channel closure
// related message, then this indicates that we're on the receiving side of
// an initiated channel closure.
// If we're in the close idle state, and we're receiving a channel
// closure related message, then this indicates that we're on the
// receiving side of an initiated channel closure.
case closeIdle:
// First, we'll assert that we have a channel shutdown message,
// as otherwise, this is an attempted invalid state transition.
shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok {
return nil, false, fmt.Errorf("expected "+
"lnwire.Shutdown, instead have %v",
spew.Sdump(msg))
}
// As we're the responder to this shutdown (the other party
// wants to close), we'll check if this is a frozen channel or
// not. If the channel is frozen and we were not also the
@ -504,12 +499,13 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
// attempt.
chanInitiator := c.cfg.Channel.IsInitiator()
if !chanInitiator {
absoluteThawHeight, err := c.cfg.Channel.AbsoluteThawHeight()
absoluteThawHeight, err :=
c.cfg.Channel.AbsoluteThawHeight()
if err != nil {
return nil, false, err
return noShutdown, err
}
if c.negotiationHeight < absoluteThawHeight {
return nil, false, fmt.Errorf("initiator "+
return noShutdown, fmt.Errorf("initiator "+
"attempting to co-op close frozen "+
"ChannelPoint(%v) (current_height=%v, "+
"thaw_height=%v)", c.chanPoint,
@ -517,102 +513,78 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
}
}
// If the remote node opened the channel with option upfront shutdown
// script, check that the script they provided matches.
// If the remote node opened the channel with option upfront
// shutdown script, check that the script they provided matches.
if err := validateShutdownScript(
c.cfg.Disconnect, c.cfg.Channel.RemoteUpfrontShutdownScript(),
shutdownMsg.Address, c.cfg.ChainParams,
c.cfg.Disconnect,
c.cfg.Channel.RemoteUpfrontShutdownScript(),
msg.Address, c.cfg.ChainParams,
); err != nil {
return nil, false, err
return noShutdown, err
}
// Once we have checked that the other party has not violated option
// upfront shutdown we set their preference for delivery address. We'll
// use this when we craft the closure transaction.
c.remoteDeliveryScript = shutdownMsg.Address
// Once we have checked that the other party has not violated
// option upfront shutdown we set their preference for delivery
// address. We'll use this when we craft the closure
// transaction.
c.remoteDeliveryScript = msg.Address
// Now that we know their desired delivery script, we can
// compute what our max/ideal fee will be.
c.initFeeBaseline()
// We'll generate a shutdown message of our own to send across the
// wire.
// We'll generate a shutdown message of our own to send across
// the wire.
localShutdown, err := c.initChanShutdown()
if err != nil {
return nil, false, err
return noShutdown, err
}
// If this is a taproot channel, then we'll want to stash the
// remote nonces so we can properly create a new musig
// session for signing.
if c.cfg.Channel.ChanType().IsTaproot() {
if shutdownMsg.ShutdownNonce == nil {
return nil, false, fmt.Errorf("shutdown " +
if msg.ShutdownNonce == nil {
return noShutdown, fmt.Errorf("shutdown " +
"nonce not populated")
}
c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
PubNonce: *shutdownMsg.ShutdownNonce,
PubNonce: *msg.ShutdownNonce,
})
}
chancloserLog.Infof("ChannelPoint(%v): responding to shutdown",
c.chanPoint)
msgsToSend := make([]lnwire.Message, 0, 2)
msgsToSend = append(msgsToSend, localShutdown)
// After the other party receives this message, we'll actually
// start the final stage of the closure process: fee
// negotiation. So we'll update our internal state to reflect
// this, so we can handle the next message sent.
c.state = closeAwaitingFlush
// After the other party receives this message, we'll actually start
// the final stage of the closure process: fee negotiation. So we'll
// update our internal state to reflect this, so we can handle the next
// message sent.
c.state = closeFeeNegotiation
return fn.Some(*localShutdown), err
// We'll also craft our initial close proposal in order to keep the
// negotiation moving, but only if we're the negotiator.
if chanInitiator {
closeSigned, err := c.proposeCloseSigned(c.idealFeeSat)
if err != nil {
return nil, false, fmt.Errorf("unable to sign "+
"new co op close offer: %w", err)
}
msgsToSend = append(msgsToSend, closeSigned)
}
// We'll return both sets of messages to send to the remote party to
// kick off the fee negotiation process.
return msgsToSend, false, nil
// If we just initiated a channel shutdown, and we receive a new message,
// then this indicates the other party is ready to shutdown as well. In
// this state we'll send our first signature.
case closeShutdownInitiated:
// First, we'll assert that we have a channel shutdown message.
// Otherwise, this is an attempted invalid state transition.
shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok {
return nil, false, fmt.Errorf("expected lnwire.Shutdown, instead "+
"have %v", spew.Sdump(msg))
}
// If the remote node opened the channel with option upfront shutdown
// script, check that the script they provided matches.
// If the remote node opened the channel with option upfront
// shutdown script, check that the script they provided matches.
if err := validateShutdownScript(
c.cfg.Disconnect,
c.cfg.Channel.RemoteUpfrontShutdownScript(), shutdownMsg.Address,
c.cfg.ChainParams,
c.cfg.Channel.RemoteUpfrontShutdownScript(),
msg.Address, c.cfg.ChainParams,
); err != nil {
return nil, false, err
return noShutdown, err
}
// Now that we know this is a valid shutdown message and address, we'll
// record their preferred delivery closing script.
c.remoteDeliveryScript = shutdownMsg.Address
// Now that we know this is a valid shutdown message and
// address, we'll record their preferred delivery closing
// script.
c.remoteDeliveryScript = msg.Address
// At this point, we can now start the fee negotiation state, by
// constructing and sending our initial signature for what we think the
// closing transaction should look like.
c.state = closeFeeNegotiation
// constructing and sending our initial signature for what we
// think the closing transaction should look like.
c.state = closeAwaitingFlush
// Now that we know their desired delivery script, we can
// compute what our max/ideal fee will be.
@ -622,34 +594,37 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
// local+remote nonces so we can properly create a new musig
// session for signing.
if c.cfg.Channel.ChanType().IsTaproot() {
if shutdownMsg.ShutdownNonce == nil {
return nil, false, fmt.Errorf("shutdown " +
if msg.ShutdownNonce == nil {
return noShutdown, fmt.Errorf("shutdown " +
"nonce not populated")
}
c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
PubNonce: *shutdownMsg.ShutdownNonce,
PubNonce: *msg.ShutdownNonce,
})
}
chancloserLog.Infof("ChannelPoint(%v): shutdown response received, "+
"entering fee negotiation", c.chanPoint)
chancloserLog.Infof("ChannelPoint(%v): shutdown response "+
"received, entering fee negotiation", c.chanPoint)
// Starting with our ideal fee rate, we'll create an initial closing
// proposal, but only if we're the initiator, as otherwise, the other
// party will send their initial proposal first.
if c.cfg.Channel.IsInitiator() {
closeSigned, err := c.proposeCloseSigned(c.idealFeeSat)
if err != nil {
return nil, false, fmt.Errorf("unable to sign "+
"new co op close offer: %w", err)
}
return noShutdown, nil
return []lnwire.Message{closeSigned}, false, nil
}
default:
// Otherwise we are not in a state where we can accept this
// message.
return noShutdown, ErrInvalidState
}
}
return nil, false, nil
// ProcessCloseMsg attempts to process the next message in the closing series.
// This method will update the state accordingly and return two primary values:
// the next set of messages to be sent, and a bool indicating if the fee
// negotiation process has completed. If the second value is true, then this
// means the ChanCloser can be garbage collected.
func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
bool, error) {
switch c.state {
// If we're receiving a message while we're in the fee negotiation phase,
// then this indicates the remote party is responding to a close signed
// message we sent, or kicking off the process with their own.