chancloser: extract ProcessCloseMsg Shutdown handling logic into dedicated method

This commit is contained in:
Keagan McClelland committed 2024-01-22 12:19:58 -08:00
1 parent 30ad49a29d
commit 6fef9ea2d7
1 file changed
+73 -98
+73 -98
View File
@@ -11,6 +11,7 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels" "github.com/lightningnetwork/lnd/labels"
@@ -475,28 +476,22 @@ func validateShutdownScript(disconnect func() error, upfrontScript,
return nil return nil
} }
// ProcessCloseMsg attempts to process the next message in the closing series. // ReceiveShutdown takes a raw Shutdown message and uses it to try and advance
// This method will update the state accordingly and return two primary values: // the ChanCloser state machine, failing if it is coming in at an invalid time.
// the next set of messages to be sent, and a bool indicating if the fee // If appropriate, it will also generate a Shutdown message of its own to send
// negotiation process has completed. If the second value is true, then this // out to the peer. It is possible for this method to return None when no error
// means the ChanCloser can be garbage collected. // occurred.
func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, func (c *ChanCloser) ReceiveShutdown(
bool, error) { msg lnwire.Shutdown,
) (fn.Option[lnwire.Shutdown], error) {
noShutdown := fn.None[lnwire.Shutdown]()
switch c.state { switch c.state {
// If we're in the close idle state, and we're receiving a channel closure // If we're in the close idle state, and we're receiving a channel
// related message, then this indicates that we're on the receiving side of // closure related message, then this indicates that we're on the
// an initiated channel closure. // receiving side of an initiated channel closure.
case closeIdle: 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 // As we're the responder to this shutdown (the other party
// wants to close), we'll check if this is a frozen channel or // 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 // 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. // attempt.
chanInitiator := c.cfg.Channel.IsInitiator() chanInitiator := c.cfg.Channel.IsInitiator()
if !chanInitiator { if !chanInitiator {
absoluteThawHeight, err := c.cfg.Channel.AbsoluteThawHeight() absoluteThawHeight, err :=
c.cfg.Channel.AbsoluteThawHeight()
if err != nil { if err != nil {
return nil, false, err return noShutdown, err
} }
if c.negotiationHeight < absoluteThawHeight { if c.negotiationHeight < absoluteThawHeight {
return nil, false, fmt.Errorf("initiator "+ return noShutdown, fmt.Errorf("initiator "+
"attempting to co-op close frozen "+ "attempting to co-op close frozen "+
"ChannelPoint(%v) (current_height=%v, "+ "ChannelPoint(%v) (current_height=%v, "+
"thaw_height=%v)", c.chanPoint, "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 // If the remote node opened the channel with option upfront
// script, check that the script they provided matches. // shutdown script, check that the script they provided matches.
if err := validateShutdownScript( if err := validateShutdownScript(
c.cfg.Disconnect, c.cfg.Channel.RemoteUpfrontShutdownScript(), c.cfg.Disconnect,
shutdownMsg.Address, c.cfg.ChainParams, c.cfg.Channel.RemoteUpfrontShutdownScript(),
msg.Address, c.cfg.ChainParams,
); err != nil { ); err != nil {
return nil, false, err return noShutdown, err
} }
// Once we have checked that the other party has not violated option // Once we have checked that the other party has not violated
// upfront shutdown we set their preference for delivery address. We'll // option upfront shutdown we set their preference for delivery
// use this when we craft the closure transaction. // address. We'll use this when we craft the closure
c.remoteDeliveryScript = shutdownMsg.Address // transaction.
c.remoteDeliveryScript = msg.Address
// Now that we know their desired delivery script, we can // Now that we know their desired delivery script, we can
// compute what our max/ideal fee will be. // compute what our max/ideal fee will be.
c.initFeeBaseline() c.initFeeBaseline()
// We'll generate a shutdown message of our own to send across the // We'll generate a shutdown message of our own to send across
// wire. // the wire.
localShutdown, err := c.initChanShutdown() localShutdown, err := c.initChanShutdown()
if err != nil { if err != nil {
return nil, false, err return noShutdown, err
} }
// If this is a taproot channel, then we'll want to stash the // If this is a taproot channel, then we'll want to stash the
// remote nonces so we can properly create a new musig // remote nonces so we can properly create a new musig
// session for signing. // session for signing.
if c.cfg.Channel.ChanType().IsTaproot() { if c.cfg.Channel.ChanType().IsTaproot() {
if shutdownMsg.ShutdownNonce == nil { if msg.ShutdownNonce == nil {
return nil, false, fmt.Errorf("shutdown " + return noShutdown, fmt.Errorf("shutdown " +
"nonce not populated") "nonce not populated")
} }
c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{ c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
PubNonce: *shutdownMsg.ShutdownNonce, PubNonce: *msg.ShutdownNonce,
}) })
} }
chancloserLog.Infof("ChannelPoint(%v): responding to shutdown", chancloserLog.Infof("ChannelPoint(%v): responding to shutdown",
c.chanPoint) c.chanPoint)
msgsToSend := make([]lnwire.Message, 0, 2) // After the other party receives this message, we'll actually
msgsToSend = append(msgsToSend, localShutdown) // 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 return fn.Some(*localShutdown), err
// 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
// 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: case closeShutdownInitiated:
// First, we'll assert that we have a channel shutdown message. // If the remote node opened the channel with option upfront
// Otherwise, this is an attempted invalid state transition. // shutdown script, check that the script they provided matches.
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 err := validateShutdownScript( if err := validateShutdownScript(
c.cfg.Disconnect, c.cfg.Disconnect,
c.cfg.Channel.RemoteUpfrontShutdownScript(), shutdownMsg.Address, c.cfg.Channel.RemoteUpfrontShutdownScript(),
c.cfg.ChainParams, msg.Address, c.cfg.ChainParams,
); err != nil { ); err != nil {
return nil, false, err return noShutdown, err
} }
// Now that we know this is a valid shutdown message and address, we'll // Now that we know this is a valid shutdown message and
// record their preferred delivery closing script. // address, we'll record their preferred delivery closing
c.remoteDeliveryScript = shutdownMsg.Address // script.
c.remoteDeliveryScript = msg.Address
// At this point, we can now start the fee negotiation state, by // At this point, we can now start the fee negotiation state, by
// constructing and sending our initial signature for what we think the // constructing and sending our initial signature for what we
// closing transaction should look like. // think the closing transaction should look like.
c.state = closeFeeNegotiation c.state = closeAwaitingFlush
// Now that we know their desired delivery script, we can // Now that we know their desired delivery script, we can
// compute what our max/ideal fee will be. // 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 // local+remote nonces so we can properly create a new musig
// session for signing. // session for signing.
if c.cfg.Channel.ChanType().IsTaproot() { if c.cfg.Channel.ChanType().IsTaproot() {
if shutdownMsg.ShutdownNonce == nil { if msg.ShutdownNonce == nil {
return nil, false, fmt.Errorf("shutdown " + return noShutdown, fmt.Errorf("shutdown " +
"nonce not populated") "nonce not populated")
} }
c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{ c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
PubNonce: *shutdownMsg.ShutdownNonce, PubNonce: *msg.ShutdownNonce,
}) })
} }
chancloserLog.Infof("ChannelPoint(%v): shutdown response received, "+ chancloserLog.Infof("ChannelPoint(%v): shutdown response "+
"entering fee negotiation", c.chanPoint) "received, entering fee negotiation", c.chanPoint)
// Starting with our ideal fee rate, we'll create an initial closing return noShutdown, nil
// 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 []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, // 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 // then this indicates the remote party is responding to a close signed
// message we sent, or kicking off the process with their own. // message we sent, or kicking off the process with their own.