chancloser: extract ProcessCloseMsg Shutdown handling logic into dedicated method
This commit is contained in:
1 parent
30ad49a29d
commit
6fef9ea2d7
1 file changed
+73
-98
@@ -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.
|
default:
|
||||||
if c.cfg.Channel.IsInitiator() {
|
// Otherwise we are not in a state where we can accept this
|
||||||
closeSigned, err := c.proposeCloseSigned(c.idealFeeSat)
|
// message.
|
||||||
if err != nil {
|
return noShutdown, ErrInvalidState
|
||||||
return nil, false, fmt.Errorf("unable to sign "+
|
}
|
||||||
"new co op close offer: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return []lnwire.Message{closeSigned}, 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
|
||||||
return nil, false, nil
|
// 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.
|
||||||
|
|||||||
Reference in new issue
Block a user