Merge pull request #4365 from Crypt-iQ/chancloser_pkg_0609

lnwallet/chancloser: cooperative channel closure pkg
This commit is contained in:
Conner Fromknecht 2020-06-18 13:38:41 -07:00 committed by GitHub
commit 5eb798a23e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 361 additions and 321 deletions

@ -1,4 +1,4 @@
package lnd package chancloser
import ( import (
"bytes" "bytes"
@ -14,24 +14,24 @@ import (
) )
var ( var (
// ErrChanAlreadyClosing is returned when a channel shutdown is // ErrChanAlreadyClosing is returned when a channel shutdown is attempted
// attempted more than once. // more than once.
ErrChanAlreadyClosing = fmt.Errorf("channel shutdown already initiated") ErrChanAlreadyClosing = fmt.Errorf("channel shutdown already initiated")
// ErrChanCloseNotFinished is returned when a caller attempts to access // ErrChanCloseNotFinished is returned when a caller attempts to access
// a field or function that is contingent on the channel closure // a field or function that is contingent on the channel closure negotiation
// negotiation already being completed. // already being completed.
ErrChanCloseNotFinished = fmt.Errorf("close negotiation not finished") ErrChanCloseNotFinished = fmt.Errorf("close negotiation not finished")
// ErrInvalidState is returned when the closing state machine receives // ErrInvalidState is returned when the closing state machine receives a
// a message while it is in an unknown state. // message while it is in an unknown state.
ErrInvalidState = fmt.Errorf("invalid state") ErrInvalidState = fmt.Errorf("invalid state")
// errUpfrontShutdownScriptMismatch is returned when a peer or end user // ErrUpfrontShutdownScriptMismatch is returned when a peer or end user
// provides a script to cooperatively close out to which does not match // provides a cooperative close script which does not match the upfront
// the upfront shutdown script previously set for that party. // shutdown script previously set for that party.
errUpfrontShutdownScriptMismatch = fmt.Errorf("shutdown " + ErrUpfrontShutdownScriptMismatch = fmt.Errorf("shutdown script does not " +
"script does not match upfront shutdown script") "match upfront shutdown script")
) )
// closeState represents all the possible states the channel closer state // closeState represents all the possible states the channel closer state
@ -43,70 +43,69 @@ type closeState uint8
const ( const (
// closeIdle is the initial starting state. In this state, the state // closeIdle is the initial starting state. In this state, the state
// machine has been instantiated, but no state transitions have been // machine has been instantiated, but no state transitions have been
// attempted. If a state machine receives a message while in this // attempted. If a state machine receives a message while in this state,
// state, then it is the responder to an initiated cooperative channel // then it is the responder to an initiated cooperative channel closure.
// closure.
closeIdle closeState = iota closeIdle closeState = iota
// closeShutdownInitiated is the state that's transitioned to once the // closeShutdownInitiated is the state that's transitioned to once the
// initiator of a closing workflow sends the shutdown message. At this // initiator of a closing workflow sends the shutdown message. At this
// point, they're waiting for the remote party to respond with their // point, they're waiting for the remote party to respond with their own
// own shutdown message. After which, they'll both enter the fee // shutdown message. After which, they'll both enter the fee negotiation
// negotiation phase. // phase.
closeShutdownInitiated closeShutdownInitiated
// closeFeeNegotiation is the third, and most persistent state. Both // closeFeeNegotiation is the third, and most persistent state. Both
// parties enter this state after they've sent and received a shutdown // parties enter this state after they've sent and received a shutdown
// message. During this phase, both sides will send monotonically // message. During this phase, both sides will send monotonically
// increasing fee requests until one side accepts the last fee rate // increasing fee requests until one side accepts the last fee rate offered
// offered by the other party. In this case, the party will broadcast // by the other party. In this case, the party will broadcast the closing
// the closing transaction, and send the accepted fee to the remote // transaction, and send the accepted fee to the remote party. This then
// party. This then causes a shift into the closeFinished state. // causes a shift into the closeFinished state.
closeFeeNegotiation closeFeeNegotiation
// closeFinished is the final state of the state machine. In this, // closeFinished is the final state of the state machine. In this state, a
// state a side has accepted a fee offer and has broadcast the valid // side has accepted a fee offer and has broadcast the valid closing
// closing transaction to the network. During this phase, the closing // transaction to the network. During this phase, the closing transaction
// transaction becomes available for examination. // becomes available for examination.
closeFinished closeFinished
) )
// chanCloseCfg holds all the items that a channelCloser requires to carry out // ChanCloseCfg holds all the items that a ChanCloser requires to carry out its
// its duties. // duties.
type chanCloseCfg struct { type ChanCloseCfg struct {
// channel is the channel that should be closed. // Channel is the channel that should be closed.
channel *lnwallet.LightningChannel Channel *lnwallet.LightningChannel
// unregisterChannel is a function closure that allows the // UnregisterChannel is a function closure that allows the ChanCloser to
// channelCloser to re-register a channel. Once this has been done, no // unregister a channel. Once this has been done, no further HTLC's should
// further HTLC's should be routed through the channel. // be routed through the channel.
unregisterChannel func(lnwire.ChannelID) UnregisterChannel func(lnwire.ChannelID)
// broadcastTx broadcasts the passed transaction to the network. // BroadcastTx broadcasts the passed transaction to the network.
broadcastTx func(*wire.MsgTx, string) error BroadcastTx func(*wire.MsgTx, string) error
// disableChannel disables a channel, resulting in it not being able to // DisableChannel disables a channel, resulting in it not being able to
// forward payments. // forward payments.
disableChannel func(wire.OutPoint) error DisableChannel func(wire.OutPoint) error
// disconnect will disconnect from the remote peer in this close. // Disconnect will disconnect from the remote peer in this close.
disconnect func() error Disconnect func() error
// quit is a channel that should be sent upon in the occasion the state // Quit is a channel that should be sent upon in the occasion the state
// machine should cease all progress and shutdown. // machine should cease all progress and shutdown.
quit chan struct{} Quit chan struct{}
} }
// channelCloser is a state machine that handles the cooperative channel // ChanCloser is a state machine that handles the cooperative channel closure
// closure procedure. This includes shutting down a channel, marking it // procedure. This includes shutting down a channel, marking it ineligible for
// ineligible for routing HTLC's, negotiating fees with the remote party, and // routing HTLC's, negotiating fees with the remote party, and finally
// finally broadcasting the fully signed closure transaction to the network. // broadcasting the fully signed closure transaction to the network.
type channelCloser struct { type ChanCloser struct {
// state is the current state of the state machine. // state is the current state of the state machine.
state closeState state closeState
// cfg holds the configuration for this channelCloser instance. // cfg holds the configuration for this ChanCloser instance.
cfg chanCloseCfg cfg ChanCloseCfg
// chanPoint is the full channel point of the target channel. // chanPoint is the full channel point of the target channel.
chanPoint wire.OutPoint chanPoint wire.OutPoint
@ -117,83 +116,81 @@ type channelCloser struct {
// negotiationHeight is the height that the fee negotiation begun at. // negotiationHeight is the height that the fee negotiation begun at.
negotiationHeight uint32 negotiationHeight uint32
// closingTx is the final, fully signed closing transaction. This will // closingTx is the final, fully signed closing transaction. This will only
// only be populated once the state machine shifts to the closeFinished // be populated once the state machine shifts to the closeFinished state.
// state.
closingTx *wire.MsgTx closingTx *wire.MsgTx
// idealFeeSat is the ideal fee that the state machine should initially // idealFeeSat is the ideal fee that the state machine should initially
// offer when starting negotiation. This will be used as a baseline. // offer when starting negotiation. This will be used as a baseline.
idealFeeSat btcutil.Amount idealFeeSat btcutil.Amount
// lastFeeProposal is the last fee that we proposed to the remote // lastFeeProposal is the last fee that we proposed to the remote party.
// party. We'll use this as a pivot point to rachet our next offer up, // We'll use this as a pivot point to ratchet our next offer up, down, or
// or down, or simply accept the remote party's prior offer. // simply accept the remote party's prior offer.
lastFeeProposal btcutil.Amount lastFeeProposal btcutil.Amount
// priorFeeOffers is a map that keeps track of all the proposed fees // priorFeeOffers is a map that keeps track of all the proposed fees that
// that we've offered during the fee negotiation. We use this map to // we've offered during the fee negotiation. We use this map to cut the
// cut the negotiation early if the remote party ever sends an offer // negotiation early if the remote party ever sends an offer that we've
// that we've sent in the past. Once negotiation terminates, we can // sent in the past. Once negotiation terminates, we can extract the prior
// extract the prior signature of our accepted offer from this map. // signature of our accepted offer from this map.
// //
// TODO(roasbeef): need to ensure if they broadcast w/ any of our prior // TODO(roasbeef): need to ensure if they broadcast w/ any of our prior
// sigs, we are aware of // sigs, we are aware of
priorFeeOffers map[btcutil.Amount]*lnwire.ClosingSigned priorFeeOffers map[btcutil.Amount]*lnwire.ClosingSigned
// closeReq is the initial closing request. This will only be populated // closeReq is the initial closing request. This will only be populated if
// if we're the initiator of this closing negotiation. // we're the initiator of this closing negotiation.
// //
// TODO(roasbeef): abstract away // TODO(roasbeef): abstract away
closeReq *htlcswitch.ChanClose closeReq *htlcswitch.ChanClose
// localDeliveryScript is the script that we'll send our settled // localDeliveryScript is the script that we'll send our settled channel
// channel funds to. // funds to.
localDeliveryScript []byte localDeliveryScript []byte
// remoteDeliveryScript is the script that we'll send the remote // remoteDeliveryScript is the script that we'll send the remote party's
// party's settled channel funds to. // settled channel funds to.
remoteDeliveryScript []byte remoteDeliveryScript []byte
// locallyInitiated is true if we initiated the channel close. // locallyInitiated is true if we initiated the channel close.
locallyInitiated bool locallyInitiated bool
} }
// newChannelCloser creates a new instance of the channel closure given the // NewChanCloser creates a new instance of the channel closure given the passed
// passed configuration, and delivery+fee preference. The final argument should // configuration, and delivery+fee preference. The final argument should only
// only be populated iff, we're the initiator of this closing request. // be populated iff, we're the initiator of this closing request.
func newChannelCloser(cfg chanCloseCfg, deliveryScript []byte, func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte,
idealFeePerKw chainfee.SatPerKWeight, negotiationHeight uint32, idealFeePerKw chainfee.SatPerKWeight, negotiationHeight uint32,
closeReq *htlcswitch.ChanClose, locallyInitiated bool) *channelCloser { closeReq *htlcswitch.ChanClose, locallyInitiated bool) *ChanCloser {
// Given the target fee-per-kw, we'll compute what our ideal _total_ // Given the target fee-per-kw, we'll compute what our ideal _total_ fee
// fee will be starting at for this fee negotiation. // will be starting at for this fee negotiation.
// //
// TODO(roasbeef): should factor in minimal commit // TODO(roasbeef): should factor in minimal commit
idealFeeSat := cfg.channel.CalcFee(idealFeePerKw) idealFeeSat := cfg.Channel.CalcFee(idealFeePerKw)
// If this fee is greater than the fee currently present within the // If this fee is greater than the fee currently present within the
// commitment transaction, then we'll clamp it down to be within the // commitment transaction, then we'll clamp it down to be within the proper
// proper range. // range.
// //
// TODO(roasbeef): clamp fee func? // TODO(roasbeef): clamp fee func?
channelCommitFee := cfg.channel.StateSnapshot().CommitFee channelCommitFee := cfg.Channel.StateSnapshot().CommitFee
if idealFeeSat > channelCommitFee { if idealFeeSat > channelCommitFee {
peerLog.Infof("Ideal starting fee of %v is greater than "+ chancloserLog.Infof("Ideal starting fee of %v is greater than commit "+
"commit fee of %v, clamping", int64(idealFeeSat), "fee of %v, clamping", int64(idealFeeSat), int64(channelCommitFee))
int64(channelCommitFee))
idealFeeSat = channelCommitFee idealFeeSat = channelCommitFee
} }
peerLog.Infof("Ideal fee for closure of ChannelPoint(%v) is: %v sat", chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) is: %v sat",
cfg.channel.ChannelPoint(), int64(idealFeeSat)) cfg.Channel.ChannelPoint(), int64(idealFeeSat))
cid := lnwire.NewChanIDFromOutPoint(cfg.channel.ChannelPoint()) cid := lnwire.NewChanIDFromOutPoint(cfg.Channel.ChannelPoint())
return &channelCloser{ return &ChanCloser{
closeReq: closeReq, closeReq: closeReq,
state: closeIdle, state: closeIdle,
chanPoint: *cfg.channel.ChannelPoint(), chanPoint: *cfg.Channel.ChannelPoint(),
cid: cid, cid: cid,
cfg: cfg, cfg: cfg,
negotiationHeight: negotiationHeight, negotiationHeight: negotiationHeight,
@ -206,37 +203,38 @@ func newChannelCloser(cfg chanCloseCfg, deliveryScript []byte,
// initChanShutdown begins the shutdown process by un-registering the channel, // initChanShutdown begins the shutdown process by un-registering the channel,
// and creating a valid shutdown message to our target delivery address. // and creating a valid shutdown message to our target delivery address.
func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) { func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
// With both items constructed we'll now send the shutdown message for // With both items constructed we'll now send the shutdown message for this
// this particular channel, advertising a shutdown request to our // particular channel, advertising a shutdown request to our desired
// desired closing script. // closing script.
shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript) shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript)
// TODO(roasbeef): err if channel has htlc's? // TODO(roasbeef): err if channel has htlc's?
// Before closing, we'll attempt to send a disable update for the // Before closing, we'll attempt to send a disable update for the channel.
// channel. We do so before closing the channel as otherwise the current // We do so before closing the channel as otherwise the current edge policy
// edge policy won't be retrievable from the graph. // won't be retrievable from the graph.
if err := c.cfg.disableChannel(c.chanPoint); err != nil { if err := c.cfg.DisableChannel(c.chanPoint); err != nil {
peerLog.Warnf("Unable to disable channel %v on "+ chancloserLog.Warnf("Unable to disable channel %v on close: %v",
"close: %v", c.chanPoint, err) c.chanPoint, err)
} }
// Before returning the shutdown message, we'll unregister the channel // Before returning the shutdown message, we'll unregister the channel to
// to ensure that it isn't seen as usable within the system. // ensure that it isn't seen as usable within the system.
c.cfg.unregisterChannel(c.cid) c.cfg.UnregisterChannel(c.cid)
// Before continuing, mark the channel as cooperatively closed with a // Before continuing, mark the channel as cooperatively closed with a nil
// nil txn. Even though we haven't negotiated the final txn, this // txn. Even though we haven't negotiated the final txn, this guarantees
// guarantees that our listchannels rpc will be externally consistent, // that our listchannels rpc will be externally consistent, and reflect
// and reflect that the channel is being shutdown by the time the // that the channel is being shutdown by the time the closing request
// closing request returns. // returns.
err := c.cfg.channel.MarkCoopBroadcasted(nil, c.locallyInitiated) err := c.cfg.Channel.MarkCoopBroadcasted(nil, c.locallyInitiated)
if err != nil { if err != nil {
return nil, err return nil, err
} }
peerLog.Infof("ChannelPoint(%v): sending shutdown message", c.chanPoint) chancloserLog.Infof("ChannelPoint(%v): sending shutdown message",
c.chanPoint)
return shutdown, nil return shutdown, nil
} }
@ -245,14 +243,14 @@ func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) {
// cooperative channel closure. This message returns the shutdown message to // cooperative channel closure. This message returns the shutdown message to
// send to the remote party. Upon completion, we enter the // send to the remote party. Upon completion, we enter the
// closeShutdownInitiated phase as we await a response. // closeShutdownInitiated phase as we await a response.
func (c *channelCloser) ShutdownChan() (*lnwire.Shutdown, error) { func (c *ChanCloser) ShutdownChan() (*lnwire.Shutdown, error) {
// If we attempt to shutdown the channel for the first time, and we're // If we attempt to shutdown the channel for the first time, and we're not
// not in the closeIdle state, then the caller made an error. // in the closeIdle state, then the caller made an error.
if c.state != closeIdle { if c.state != closeIdle {
return nil, ErrChanAlreadyClosing return nil, ErrChanAlreadyClosing
} }
peerLog.Infof("ChannelPoint(%v): initiating shutdown of", c.chanPoint) chancloserLog.Infof("ChannelPoint(%v): initiating shutdown", c.chanPoint)
shutdownMsg, err := c.initChanShutdown() shutdownMsg, err := c.initChanShutdown()
if err != nil { if err != nil {
@ -260,12 +258,12 @@ func (c *channelCloser) ShutdownChan() (*lnwire.Shutdown, error) {
} }
// With the opening steps complete, we'll transition into the // With the opening steps complete, we'll transition into the
// closeShutdownInitiated state. In this state, we'll wait until the // closeShutdownInitiated state. In this state, we'll wait until the other
// other party sends their version of the shutdown message. // party sends their version of the shutdown message.
c.state = closeShutdownInitiated c.state = closeShutdownInitiated
// Finally, we'll return the shutdown message to the caller so it can // Finally, we'll return the shutdown message to the caller so it can send
// send it to the remote peer. // it to the remote peer.
return shutdownMsg, nil return shutdownMsg, nil
} }
@ -273,8 +271,8 @@ func (c *channelCloser) ShutdownChan() (*lnwire.Shutdown, error) {
// //
// NOTE: This transaction is only available if the state machine is in the // NOTE: This transaction is only available if the state machine is in the
// closeFinished state. // closeFinished state.
func (c *channelCloser) ClosingTx() (*wire.MsgTx, error) { func (c *ChanCloser) ClosingTx() (*wire.MsgTx, error) {
// If the state machine hasn't finished closing the channel then we'll // If the state machine hasn't finished closing the channel, then we'll
// return an error as we haven't yet computed the closing tx. // return an error as we haven't yet computed the closing tx.
if c.state != closeFinished { if c.state != closeFinished {
return nil, ErrChanCloseNotFinished return nil, ErrChanCloseNotFinished
@ -288,19 +286,28 @@ func (c *channelCloser) ClosingTx() (*wire.MsgTx, error) {
// //
// NOTE: This will only return a non-nil pointer if we were the initiator of // NOTE: This will only return a non-nil pointer if we were the initiator of
// the cooperative closure workflow. // the cooperative closure workflow.
func (c *channelCloser) CloseRequest() *htlcswitch.ChanClose { func (c *ChanCloser) CloseRequest() *htlcswitch.ChanClose {
return c.closeReq return c.closeReq
} }
// Channel returns the channel stored in the config.
func (c *ChanCloser) Channel() *lnwallet.LightningChannel {
return c.cfg.Channel
}
// NegotiationHeight returns the negotiation height.
func (c *ChanCloser) NegotiationHeight() uint32 {
return c.negotiationHeight
}
// maybeMatchScript attempts to match the script provided in our peer's // maybeMatchScript attempts to match the script provided in our peer's
// shutdown message with the upfront shutdown script we have on record. // shutdown message with the upfront shutdown script we have on record. If no
// If no upfront shutdown script was set, we do not need to enforce option // upfront shutdown script was set, we do not need to enforce option upfront
// upfront shutdown, so the function returns early. If an upfront script is // shutdown, so the function returns early. If an upfront script is set, we
// set, we check whether it matches the script provided by our peer. If they // check whether it matches the script provided by our peer. If they do not
// do not match, we use the disconnect function provided to disconnect from // match, we use the disconnect function provided to disconnect from the peer.
// the peer. func maybeMatchScript(disconnect func() error, upfrontScript,
func maybeMatchScript(disconnect func() error, peerScript lnwire.DeliveryAddress) error {
upfrontScript, peerScript lnwire.DeliveryAddress) error {
// If no upfront shutdown script was set, return early because we do not // If no upfront shutdown script was set, return early because we do not
// need to enforce closure to a specific script. // need to enforce closure to a specific script.
@ -311,7 +318,7 @@ func maybeMatchScript(disconnect func() error,
// If an upfront shutdown script was provided, disconnect from the peer, as // If an upfront shutdown script was provided, disconnect from the peer, as
// per BOLT 2, and return an error. // per BOLT 2, and return an error.
if !bytes.Equal(upfrontScript, peerScript) { if !bytes.Equal(upfrontScript, peerScript) {
peerLog.Warnf("peer's script: %x does not match upfront "+ chancloserLog.Warnf("peer's script: %x does not match upfront "+
"shutdown script: %x", peerScript, upfrontScript) "shutdown script: %x", peerScript, upfrontScript)
// Disconnect from the peer because they have violated option upfront // Disconnect from the peer because they have violated option upfront
@ -320,7 +327,7 @@ func maybeMatchScript(disconnect func() error,
return err return err
} }
return errUpfrontShutdownScriptMismatch return ErrUpfrontShutdownScriptMismatch
} }
return nil return nil
@ -330,42 +337,44 @@ func maybeMatchScript(disconnect func() error,
// This method will update the state accordingly and return two primary values: // 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 // 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 // negotiation process has completed. If the second value is true, then this
// means the channelCloser can be garbage collected. // means the ChanCloser can be garbage collected.
func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, bool, error) { func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
bool, error) {
switch c.state { switch c.state {
// If we're in the close idle state, and we're receiving a channel // If we're in the close idle state, and we're receiving a channel closure
// closure related message, then this indicates that we're on the // related message, then this indicates that we're on the receiving side of
// receiving side of an initiated channel closure. // an initiated channel closure.
case closeIdle: case closeIdle:
// First, we'll assert that we have a channel shutdown message, // First, we'll assert that we have a channel shutdown message,
// otherwise, this is an attempted invalid state transition. // as otherwise, this is an attempted invalid state transition.
shutDownMsg, ok := msg.(*lnwire.Shutdown) shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok { if !ok {
return nil, false, fmt.Errorf("expected lnwire.Shutdown, "+ return nil, false, fmt.Errorf("expected lnwire.Shutdown, instead "+
"instead have %v", spew.Sdump(msg)) "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
// wants to close), we'll check if this is a frozen channel or // close), we'll check if this is a frozen channel or not. If the
// not. If the channel is frozen as we were also the initiator // channel is frozen and we were not also the initiator of the channel
// of the channel opening, then we'll deny their close attempt. // opening, then we'll deny their close attempt.
chanInitiator := c.cfg.channel.IsInitiator() chanInitiator := c.cfg.Channel.IsInitiator()
if !chanInitiator && c.cfg.channel.State().ChanType.IsFrozen() && chanState := c.cfg.Channel.State()
c.negotiationHeight < c.cfg.channel.State().ThawHeight { if !chanInitiator && chanState.ChanType.IsFrozen() &&
c.negotiationHeight < chanState.ThawHeight {
return nil, false, fmt.Errorf("initiator attempting "+ return nil, false, fmt.Errorf("initiator attempting to co-op "+
"to co-op close frozen ChannelPoint(%v) "+ "close frozen ChannelPoint(%v) (current_height=%v, "+
"(current_height=%v, thaw_height=%v)", "thaw_height=%v)", c.chanPoint, c.negotiationHeight,
c.chanPoint, c.negotiationHeight, chanState.ThawHeight)
c.cfg.channel.State().ThawHeight)
} }
// If the remote node opened the channel with option upfront shutdown // If the remote node opened the channel with option upfront shutdown
// script, check that the script they provided matches. // script, check that the script they provided matches.
if err := maybeMatchScript( if err := maybeMatchScript(
c.cfg.disconnect, c.cfg.channel.RemoteUpfrontShutdownScript(), c.cfg.Disconnect, c.cfg.Channel.RemoteUpfrontShutdownScript(),
shutDownMsg.Address, shutdownMsg.Address,
); err != nil { ); err != nil {
return nil, false, err return nil, false, err
} }
@ -373,29 +382,29 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b
// Once we have checked that the other party has not violated option // Once we have checked that the other party has not violated option
// upfront shutdown we set their preference for delivery address. We'll // upfront shutdown we set their preference for delivery address. We'll
// use this when we craft the closure transaction. // use this when we craft the closure transaction.
c.remoteDeliveryScript = shutDownMsg.Address c.remoteDeliveryScript = shutdownMsg.Address
// We'll generate a shutdown message of our own to send across // We'll generate a shutdown message of our own to send across the
// the wire. // wire.
localShutdown, err := c.initChanShutdown() localShutdown, err := c.initChanShutdown()
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
peerLog.Infof("ChannelPoint(%v): Responding to shutdown", chancloserLog.Infof("ChannelPoint(%v): responding to shutdown",
c.chanPoint) c.chanPoint)
msgsToSend := make([]lnwire.Message, 0, 2) msgsToSend := make([]lnwire.Message, 0, 2)
msgsToSend = append(msgsToSend, localShutdown) msgsToSend = append(msgsToSend, localShutdown)
// After the other party receives this message, we'll actually // After the other party receives this message, we'll actually start
// start the final stage of the closure process: fee // the final stage of the closure process: fee negotiation. So we'll
// negotiation. So we'll update our internal state to reflect // update our internal state to reflect this, so we can handle the next
// this, so we can handle the next message sent. // message sent.
c.state = closeFeeNegotiation c.state = closeFeeNegotiation
// We'll also craft our initial close proposal in order to keep // We'll also craft our initial close proposal in order to keep the
// the negotiation moving, but only if we're the negotiator. // negotiation moving, but only if we're the negotiator.
if chanInitiator { if chanInitiator {
closeSigned, err := c.proposeCloseSigned(c.idealFeeSat) closeSigned, err := c.proposeCloseSigned(c.idealFeeSat)
if err != nil { if err != nil {
@ -404,48 +413,46 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b
msgsToSend = append(msgsToSend, closeSigned) msgsToSend = append(msgsToSend, closeSigned)
} }
// We'll return both sets of messages to send to the remote // We'll return both sets of messages to send to the remote party to
// party to kick off the fee negotiation process. // kick off the fee negotiation process.
return msgsToSend, false, nil return msgsToSend, false, nil
// If we just initiated a channel shutdown, and we receive a new // If we just initiated a channel shutdown, and we receive a new message,
// message, then this indicates the other party is ready to shutdown as // then this indicates the other party is ready to shutdown as well. In
// well. In this state we'll send our first signature. // this state we'll send our first signature.
case closeShutdownInitiated: case closeShutdownInitiated:
// First, we'll assert that we have a channel shutdown message, // First, we'll assert that we have a channel shutdown message.
// otherwise, this is an attempted invalid state transition. // Otherwise, this is an attempted invalid state transition.
shutDownMsg, ok := msg.(*lnwire.Shutdown) shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok { if !ok {
return nil, false, fmt.Errorf("expected lnwire.Shutdown, "+ return nil, false, fmt.Errorf("expected lnwire.Shutdown, instead "+
"instead have %v", spew.Sdump(msg)) "have %v", spew.Sdump(msg))
} }
// If the remote node opened the channel with option upfront shutdown // If the remote node opened the channel with option upfront shutdown
// script, check that the script they provided matches. // script, check that the script they provided matches.
if err := maybeMatchScript( if err := maybeMatchScript(c.cfg.Disconnect,
c.cfg.disconnect, c.cfg.channel.RemoteUpfrontShutdownScript(), c.cfg.Channel.RemoteUpfrontShutdownScript(), shutdownMsg.Address,
shutDownMsg.Address,
); err != nil { ); err != nil {
return nil, false, err return nil, false, 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 address, we'll
// record their preferred delivery closing script. // record their preferred delivery closing script.
c.remoteDeliveryScript = shutDownMsg.Address c.remoteDeliveryScript = shutdownMsg.Address
// At this point, we can now start the fee negotiation state, // At this point, we can now start the fee negotiation state, by
// by constructing and sending our initial signature for what // constructing and sending our initial signature for what we think the
// we think the closing transaction should look like. // closing transaction should look like.
c.state = closeFeeNegotiation c.state = closeFeeNegotiation
peerLog.Infof("ChannelPoint(%v): shutdown response received, "+ chancloserLog.Infof("ChannelPoint(%v): shutdown response received, "+
"entering fee negotiation", c.chanPoint) "entering fee negotiation", c.chanPoint)
// Starting with our ideal fee rate, we'll create an initial // Starting with our ideal fee rate, we'll create an initial closing
// closing proposal, but only if we're the initiator, as // proposal, but only if we're the initiator, as otherwise, the other
// otherwise, the other party will send their first proposal // party will send their initial proposal first.
// first. if c.cfg.Channel.IsInitiator() {
if c.cfg.channel.IsInitiator() {
closeSigned, err := c.proposeCloseSigned(c.idealFeeSat) closeSigned, err := c.proposeCloseSigned(c.idealFeeSat)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
@ -456,58 +463,54 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b
return nil, false, nil return nil, false, nil
// If we're receiving a message while we're in the fee negotiation // If we're receiving a message while we're in the fee negotiation phase,
// phase, then this indicates the remote party is responding to a closed // then this indicates the remote party is responding to a close signed
// signed message we sent, or kicking off the process with their own. // message we sent, or kicking off the process with their own.
case closeFeeNegotiation: case closeFeeNegotiation:
// First, we'll assert that we're actually getting a // First, we'll assert that we're actually getting a ClosingSigned
// CloseSigned message, otherwise an invalid state transition // message, otherwise an invalid state transition was attempted.
// was attempted.
closeSignedMsg, ok := msg.(*lnwire.ClosingSigned) closeSignedMsg, ok := msg.(*lnwire.ClosingSigned)
if !ok { if !ok {
return nil, false, fmt.Errorf("expected lnwire.ClosingSigned, "+ return nil, false, fmt.Errorf("expected lnwire.ClosingSigned, "+
"instead have %v", spew.Sdump(msg)) "instead have %v", spew.Sdump(msg))
} }
// We'll compare the proposed total fee, to what we've proposed // We'll compare the proposed total fee, to what we've proposed during
// during the negotiations, if it doesn't match any of our // the negotiations. If it doesn't match any of our prior offers, then
// prior offers, then we'll attempt to rachet the fee closer to // we'll attempt to ratchet the fee closer to
remoteProposedFee := closeSignedMsg.FeeSatoshis remoteProposedFee := closeSignedMsg.FeeSatoshis
if _, ok := c.priorFeeOffers[remoteProposedFee]; !ok { if _, ok := c.priorFeeOffers[remoteProposedFee]; !ok {
// We'll now attempt to rachet towards a fee deemed // We'll now attempt to ratchet towards a fee deemed acceptable by
// acceptable by both parties, factoring in our ideal // both parties, factoring in our ideal fee rate, and the last
// fee rate, and the last proposed fee by both sides. // proposed fee by both sides.
feeProposal := calcCompromiseFee(c.chanPoint, feeProposal := calcCompromiseFee(c.chanPoint, c.idealFeeSat,
c.idealFeeSat, c.lastFeeProposal, c.lastFeeProposal, remoteProposedFee,
remoteProposedFee,
) )
// With our new fee proposal calculated, we'll craft a // With our new fee proposal calculated, we'll craft a new close
// new close signed signature to send to the other // signed signature to send to the other party so we can continue
// party so we can continue the fee negotiation // the fee negotiation process.
// process.
closeSigned, err := c.proposeCloseSigned(feeProposal) closeSigned, err := c.proposeCloseSigned(feeProposal)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
// If the compromise fee doesn't match what the peer // If the compromise fee doesn't match what the peer proposed, then
// proposed, then we'll return this latest close signed // we'll return this latest close signed message so we can continue
// message so we continue negotiation. // negotiation.
if feeProposal != remoteProposedFee { if feeProposal != remoteProposedFee {
peerLog.Debugf("ChannelPoint(%v): close tx "+ chancloserLog.Debugf("ChannelPoint(%v): close tx fee "+
"fee disagreement, continuing negotiation", "disagreement, continuing negotiation", c.chanPoint)
c.chanPoint)
return []lnwire.Message{closeSigned}, false, nil return []lnwire.Message{closeSigned}, false, nil
} }
} }
peerLog.Infof("ChannelPoint(%v) fee of %v accepted, ending "+ chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, ending "+
"negotiation", c.chanPoint, remoteProposedFee) "negotiation", c.chanPoint, remoteProposedFee)
// Otherwise, we've agreed on a fee for the closing // Otherwise, we've agreed on a fee for the closing transaction! We'll
// transaction! We'll craft the final closing transaction so // craft the final closing transaction so we can broadcast it to the
// we can broadcast it to the network. // network.
matchingSig := c.priorFeeOffers[remoteProposedFee].Signature matchingSig := c.priorFeeOffers[remoteProposedFee].Signature
localSig, err := matchingSig.ToSignature() localSig, err := matchingSig.ToSignature()
if err != nil { if err != nil {
@ -519,57 +522,52 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b
return nil, false, err return nil, false, err
} }
closeTx, _, err := c.cfg.channel.CompleteCooperativeClose( closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose(
localSig, remoteSig, c.localDeliveryScript, localSig, remoteSig, c.localDeliveryScript, c.remoteDeliveryScript,
c.remoteDeliveryScript, remoteProposedFee, remoteProposedFee,
) )
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
c.closingTx = closeTx c.closingTx = closeTx
// Before publishing the closing tx, we persist it to the // Before publishing the closing tx, we persist it to the database,
// database, such that it can be republished if something goes // such that it can be republished if something goes wrong.
// wrong. err = c.cfg.Channel.MarkCoopBroadcasted(closeTx, c.locallyInitiated)
err = c.cfg.channel.MarkCoopBroadcasted(
closeTx, c.locallyInitiated,
)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
// With the closing transaction crafted, we'll now broadcast it // With the closing transaction crafted, we'll now broadcast it to the
// to the network. // network.
peerLog.Infof("Broadcasting cooperative close tx: %v", chancloserLog.Infof("Broadcasting cooperative close tx: %v",
newLogClosure(func() string { newLogClosure(func() string {
return spew.Sdump(closeTx) return spew.Sdump(closeTx)
})) }),
err = c.cfg.broadcastTx(closeTx, "") )
if err != nil { if err := c.cfg.BroadcastTx(closeTx, ""); err != nil {
return nil, false, err return nil, false, err
} }
// Finally, we'll transition to the closeFinished state, and // Finally, we'll transition to the closeFinished state, and also
// also return the final close signed message we sent. // return the final close signed message we sent. Additionally, we
// Additionally, we return true for the second argument to // return true for the second argument to indicate we're finished with
// indicate we're finished with the channel closing // the channel closing negotiation.
// negotiation.
c.state = closeFinished c.state = closeFinished
matchingOffer := c.priorFeeOffers[remoteProposedFee] matchingOffer := c.priorFeeOffers[remoteProposedFee]
return []lnwire.Message{matchingOffer}, true, nil return []lnwire.Message{matchingOffer}, true, nil
// If we receive a message while in the closeFinished state, then this // If we received a message while in the closeFinished state, then this
// should only be the remote party echoing the last ClosingSigned // should only be the remote party echoing the last ClosingSigned message
// message that we agreed on. // that we agreed on.
case closeFinished: case closeFinished:
if _, ok := msg.(*lnwire.ClosingSigned); !ok { if _, ok := msg.(*lnwire.ClosingSigned); !ok {
return nil, false, fmt.Errorf("expected "+ return nil, false, fmt.Errorf("expected lnwire.ClosingSigned, "+
"lnwire.ClosingSigned, instead have %v", "instead have %v", spew.Sdump(msg))
spew.Sdump(msg))
} }
// There's no more to do as both sides should have already // There's no more to do as both sides should have already broadcast
// broadcast the closing transaction at this state. // the closing transaction at this state.
return nil, true, nil return nil, true, nil
// Otherwise, we're in an unknown state, and can't proceed. // Otherwise, we're in an unknown state, and can't proceed.
@ -579,31 +577,30 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b
} }
// proposeCloseSigned attempts to propose a new signature for the closing // proposeCloseSigned attempts to propose a new signature for the closing
// transaction for a channel based on the prior fee negotiations and our // transaction for a channel based on the prior fee negotiations and our current
// current compromise fee. // compromise fee.
func (c *channelCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSigned, error) { func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSigned, error) {
rawSig, _, _, err := c.cfg.channel.CreateCloseProposal( rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal(
fee, c.localDeliveryScript, c.remoteDeliveryScript, fee, c.localDeliveryScript, c.remoteDeliveryScript,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
// We'll note our last signature and proposed fee so when the remote // We'll note our last signature and proposed fee so when the remote party
// party responds we'll be able to decide if we've agreed on fees or // responds we'll be able to decide if we've agreed on fees or not.
// not.
c.lastFeeProposal = fee c.lastFeeProposal = fee
parsedSig, err := lnwire.NewSigFromSignature(rawSig) parsedSig, err := lnwire.NewSigFromSignature(rawSig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
peerLog.Infof("ChannelPoint(%v): proposing fee of %v sat to close "+ chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to close "+
"chan", c.chanPoint, int64(fee)) "chan", c.chanPoint, int64(fee))
// We'll assemble a ClosingSigned message using this information and // We'll assemble a ClosingSigned message using this information and return
// return it to the caller so we can kick off the final stage of the // it to the caller so we can kick off the final stage of the channel
// channel closure project. // closure process.
closeSignedMsg := lnwire.NewClosingSigned(c.cid, fee, parsedSig) closeSignedMsg := lnwire.NewClosingSigned(c.cid, fee, parsedSig)
// We'll also save this close signed, in the case that the remote party // We'll also save this close signed, in the case that the remote party
@ -618,26 +615,25 @@ func (c *channelCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingS
// compromise and to ensure that the fee negotiation has a stopping point. We // compromise and to ensure that the fee negotiation has a stopping point. We
// consider their fee acceptable if it's within 30% of our fee. // consider their fee acceptable if it's within 30% of our fee.
func feeInAcceptableRange(localFee, remoteFee btcutil.Amount) bool { func feeInAcceptableRange(localFee, remoteFee btcutil.Amount) bool {
// If our offer is lower than theirs, then we'll accept their // If our offer is lower than theirs, then we'll accept their offer if it's
// offer if it's no more than 30% *greater* than our current // no more than 30% *greater* than our current offer.
// offer.
if localFee < remoteFee { if localFee < remoteFee {
acceptableRange := localFee + ((localFee * 3) / 10) acceptableRange := localFee + ((localFee * 3) / 10)
return remoteFee <= acceptableRange return remoteFee <= acceptableRange
} }
// If our offer is greater than theirs, then we'll accept their offer // If our offer is greater than theirs, then we'll accept their offer if
// if it's no more than 30% *less* than our current offer. // it's no more than 30% *less* than our current offer.
acceptableRange := localFee - ((localFee * 3) / 10) acceptableRange := localFee - ((localFee * 3) / 10)
return remoteFee >= acceptableRange return remoteFee >= acceptableRange
} }
// rachetFee is our step function used to inch our fee closer to something that // ratchetFee is our step function used to inch our fee closer to something
// both sides can agree on. If up is true, then we'll attempt to increase our // that both sides can agree on. If up is true, then we'll attempt to increase
// offered fee. Otherwise, if up is false, then we'll attempt to decrease our // our offered fee. Otherwise, if up is false, then we'll attempt to decrease
// offered fee. // our offered fee.
func rachetFee(fee btcutil.Amount, up bool) btcutil.Amount { func ratchetFee(fee btcutil.Amount, up bool) btcutil.Amount {
// If we need to rachet up, then we'll increase our fee by 10%. // If we need to ratchet up, then we'll increase our fee by 10%.
if up { if up {
return fee + ((fee * 1) / 10) return fee + ((fee * 1) / 10)
} }
@ -649,62 +645,58 @@ func rachetFee(fee btcutil.Amount, up bool) btcutil.Amount {
// calcCompromiseFee performs the current fee negotiation algorithm, taking // calcCompromiseFee performs the current fee negotiation algorithm, taking
// into consideration our ideal fee based on current fee environment, the fee // into consideration our ideal fee based on current fee environment, the fee
// we last proposed (if any), and the fee proposed by the peer. // we last proposed (if any), and the fee proposed by the peer.
func calcCompromiseFee(chanPoint wire.OutPoint, func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee,
ourIdealFee, lastSentFee, remoteFee btcutil.Amount) btcutil.Amount { remoteFee btcutil.Amount) btcutil.Amount {
// TODO(roasbeef): take in number of rounds as well? // TODO(roasbeef): take in number of rounds as well?
peerLog.Infof("ChannelPoint(%v): computing fee compromise, ideal=%v, "+ chancloserLog.Infof("ChannelPoint(%v): computing fee compromise, ideal="+
"last_sent=%v, remote_offer=%v", chanPoint, int64(ourIdealFee), "%v, last_sent=%v, remote_offer=%v", chanPoint, int64(ourIdealFee),
int64(lastSentFee), int64(remoteFee)) int64(lastSentFee), int64(remoteFee))
// Otherwise, we'll need to attempt to make a fee compromise if this is // Otherwise, we'll need to attempt to make a fee compromise if this is the
// the second round, and neither side has agreed on fees. // second round, and neither side has agreed on fees.
switch { switch {
// If their proposed fee is identical to our ideal fee, then we'll go // If their proposed fee is identical to our ideal fee, then we'll go with
// with that as we can short circuit the fee negotiation. Similarly, if // that as we can short circuit the fee negotiation. Similarly, if we
// we haven't sent an offer yet, we'll default to our ideal fee. // haven't sent an offer yet, we'll default to our ideal fee.
case ourIdealFee == remoteFee || lastSentFee == 0: case ourIdealFee == remoteFee || lastSentFee == 0:
return ourIdealFee return ourIdealFee
// If the last fee we sent, is equal to the fee the remote party is // If the last fee we sent, is equal to the fee the remote party is
// offering, then we can simply return this fee as the negotiation is // offering, then we can simply return this fee as the negotiation is over.
// over.
case remoteFee == lastSentFee: case remoteFee == lastSentFee:
return lastSentFee return lastSentFee
// If the fee the remote party is offering is less than the last one we // If the fee the remote party is offering is less than the last one we
// sent, then we'll need to rachet down in order to move our offer // sent, then we'll need to ratchet down in order to move our offer closer
// closer to theirs. // to theirs.
case remoteFee < lastSentFee: case remoteFee < lastSentFee:
// If the fee is lower, but still acceptable, then we'll just // If the fee is lower, but still acceptable, then we'll just return
// return this fee and end the negotiation. // this fee and end the negotiation.
if feeInAcceptableRange(lastSentFee, remoteFee) { if feeInAcceptableRange(lastSentFee, remoteFee) {
peerLog.Infof("ChannelPoint(%v): proposed remote fee "+ chancloserLog.Infof("ChannelPoint(%v): proposed remote fee is "+
"is close enough, capitulating", chanPoint) "close enough, capitulating", chanPoint)
return remoteFee return remoteFee
} }
// Otherwise, we'll rachet the fee *down* using our current // Otherwise, we'll ratchet the fee *down* using our current algorithm.
// algorithm. return ratchetFee(lastSentFee, false)
return rachetFee(lastSentFee, false)
// If the fee the remote party is offering is greater than the last one // If the fee the remote party is offering is greater than the last one we
// we sent, then we'll rachet up in order to ensure we terminate // sent, then we'll ratchet up in order to ensure we terminate eventually.
// eventually.
case remoteFee > lastSentFee: case remoteFee > lastSentFee:
// If the fee is greater, but still acceptable, then we'll just // If the fee is greater, but still acceptable, then we'll just return
// return this fee in order to put an end to the negotiation. // this fee in order to put an end to the negotiation.
if feeInAcceptableRange(lastSentFee, remoteFee) { if feeInAcceptableRange(lastSentFee, remoteFee) {
peerLog.Infof("ChannelPoint(%v): proposed remote fee "+ chancloserLog.Infof("ChannelPoint(%v): proposed remote fee is "+
"is close enough, capitulating", chanPoint) "close enough, capitulating", chanPoint)
return remoteFee return remoteFee
} }
// Otherwise, we'll rachet the fee up using our current // Otherwise, we'll ratchet the fee up using our current algorithm.
// algorithm. return ratchetFee(lastSentFee, true)
return rachetFee(lastSentFee, true)
default: default:
// TODO(roasbeef): fail if their fee isn't in expected range // TODO(roasbeef): fail if their fee isn't in expected range

@ -1,4 +1,4 @@
package lnd package chancloser
import ( import (
"crypto/rand" "crypto/rand"
@ -49,7 +49,7 @@ func TestMaybeMatchScript(t *testing.T) {
name: "upfront shutdown set, script not ok", name: "upfront shutdown set, script not ok",
shutdownScript: addr1, shutdownScript: addr1,
upfrontScript: addr2, upfrontScript: addr2,
expectedErr: errUpfrontShutdownScriptMismatch, expectedErr: ErrUpfrontShutdownScriptMismatch,
}, },
{ {
name: "nil shutdown and empty upfront", name: "nil shutdown and empty upfront",

@ -0,0 +1,41 @@
package chancloser
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)
// chancloserLog is a logger that is initialized with the btclog.Disabled
// logger.
var chancloserLog btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("CHCL", nil))
}
// DisableLog disables all logging output.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
chancloserLog = logger
}
// logClosure is used to provide a closure over expensive logging operations
// so they aren't performed when the logging level doesn't warrant it.
type logClosure func() string
// String invokes the underlying function and returns the result.
func (c logClosure) String() string {
return c()
}
// newLogClosure returns a new closure over a function that returns a string
// which itself provides a Stringer interface so that it can be used with the
// logging system.
func newLogClosure(c func() string) logClosure {
return logClosure(c)
}

2
log.go

@ -26,6 +26,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/verrpc" "github.com/lightningnetwork/lnd/lnrpc/verrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/monitoring" "github.com/lightningnetwork/lnd/monitoring"
"github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/netann"
@ -121,6 +122,7 @@ func SetupLoggers(root *build.RotatingLogWriter) {
AddSubLogger(root, "WTCL", wtclient.UseLogger) AddSubLogger(root, "WTCL", wtclient.UseLogger)
AddSubLogger(root, "PRNF", peernotifier.UseLogger) AddSubLogger(root, "PRNF", peernotifier.UseLogger)
AddSubLogger(root, "CHFD", chanfunding.UseLogger) AddSubLogger(root, "CHFD", chanfunding.UseLogger)
AddSubLogger(root, "CHCL", chancloser.UseLogger)
AddSubLogger(root, routing.Subsystem, routing.UseLogger, localchans.UseLogger) AddSubLogger(root, routing.Subsystem, routing.UseLogger, localchans.UseLogger)
AddSubLogger(root, routerrpc.Subsystem, routerrpc.UseLogger) AddSubLogger(root, routerrpc.Subsystem, routerrpc.UseLogger)

51
peer.go

@ -27,6 +27,7 @@ import (
"github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/pool" "github.com/lightningnetwork/lnd/pool"
"github.com/lightningnetwork/lnd/queue" "github.com/lightningnetwork/lnd/queue"
@ -190,7 +191,7 @@ type peer struct {
// messages are directed to one of these active state machines. Once // messages are directed to one of these active state machines. Once
// the channel has been closed, the state machine will be delete from // the channel has been closed, the state machine will be delete from
// the map. // the map.
activeChanCloses map[lnwire.ChannelID]*channelCloser activeChanCloses map[lnwire.ChannelID]*chancloser.ChanCloser
// localCloseChanReqs is a channel in which any local requests to close // localCloseChanReqs is a channel in which any local requests to close
// a particular channel are sent over. // a particular channel are sent over.
@ -302,7 +303,7 @@ func newPeer(cfg *Config, conn net.Conn, connReq *connmgr.ConnReq, server *serve
activeMsgStreams: make(map[lnwire.ChannelID]*msgStream), activeMsgStreams: make(map[lnwire.ChannelID]*msgStream),
activeChanCloses: make(map[lnwire.ChannelID]*channelCloser), activeChanCloses: make(map[lnwire.ChannelID]*chancloser.ChanCloser),
localCloseChanReqs: make(chan *htlcswitch.ChanClose), localCloseChanReqs: make(chan *htlcswitch.ChanClose),
linkFailures: make(chan linkFailureReport), linkFailures: make(chan linkFailureReport),
chanCloseMsgs: make(chan *closeMsg), chanCloseMsgs: make(chan *closeMsg),
@ -2114,7 +2115,7 @@ out:
// As the negotiations failed, we'll reset the // As the negotiations failed, we'll reset the
// channel state to ensure we act to on-chain // channel state to ensure we act to on-chain
// events as normal. // events as normal.
chanCloser.cfg.channel.ResetState() chanCloser.Channel().ResetState()
if chanCloser.CloseRequest() != nil { if chanCloser.CloseRequest() != nil {
chanCloser.CloseRequest().Err <- err chanCloser.CloseRequest().Err <- err
@ -2232,7 +2233,9 @@ func (p *peer) reenableActiveChannels() {
// for the target channel ID. If the channel isn't active an error is returned. // for the target channel ID. If the channel isn't active an error is returned.
// Otherwise, either an existing state machine will be returned, or a new one // Otherwise, either an existing state machine will be returned, or a new one
// will be created. // will be created.
func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, error) { func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (
*chancloser.ChanCloser, error) {
// First, we'll ensure that we actually know of the target channel. If // First, we'll ensure that we actually know of the target channel. If
// not, we'll ignore this message. // not, we'll ignore this message.
p.activeChanMtx.RLock() p.activeChanMtx.RLock()
@ -2288,16 +2291,16 @@ func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, e
return nil, fmt.Errorf("cannot obtain best block") return nil, fmt.Errorf("cannot obtain best block")
} }
chanCloser = newChannelCloser( chanCloser = chancloser.NewChanCloser(
chanCloseCfg{ chancloser.ChanCloseCfg{
channel: channel, Channel: channel,
unregisterChannel: p.server.htlcSwitch.RemoveLink, UnregisterChannel: p.server.htlcSwitch.RemoveLink,
broadcastTx: p.server.cc.wallet.PublishTransaction, BroadcastTx: p.server.cc.wallet.PublishTransaction,
disableChannel: p.server.chanStatusMgr.RequestDisable, DisableChannel: p.server.chanStatusMgr.RequestDisable,
disconnect: func() error { Disconnect: func() error {
return p.server.DisconnectPeer(p.IdentityKey()) return p.server.DisconnectPeer(p.IdentityKey())
}, },
quit: p.quit, Quit: p.quit,
}, },
deliveryScript, deliveryScript,
feePerKw, feePerKw,
@ -2334,7 +2337,7 @@ func chooseDeliveryScript(upfront,
// the upfront shutdown script (because closing out to a different script // the upfront shutdown script (because closing out to a different script
// would violate upfront shutdown). // would violate upfront shutdown).
if !bytes.Equal(upfront, requested) { if !bytes.Equal(upfront, requested) {
return nil, errUpfrontShutdownScriptMismatch return nil, chancloser.ErrUpfrontShutdownScriptMismatch
} }
// The user requested script matches the upfront shutdown script, so we // The user requested script matches the upfront shutdown script, so we
@ -2404,16 +2407,16 @@ func (p *peer) handleLocalCloseReq(req *htlcswitch.ChanClose) {
return return
} }
chanCloser := newChannelCloser( chanCloser := chancloser.NewChanCloser(
chanCloseCfg{ chancloser.ChanCloseCfg{
channel: channel, Channel: channel,
unregisterChannel: p.server.htlcSwitch.RemoveLink, UnregisterChannel: p.server.htlcSwitch.RemoveLink,
broadcastTx: p.server.cc.wallet.PublishTransaction, BroadcastTx: p.server.cc.wallet.PublishTransaction,
disableChannel: p.server.chanStatusMgr.RequestDisable, DisableChannel: p.server.chanStatusMgr.RequestDisable,
disconnect: func() error { Disconnect: func() error {
return p.server.DisconnectPeer(p.IdentityKey()) return p.server.DisconnectPeer(p.IdentityKey())
}, },
quit: p.quit, Quit: p.quit,
}, },
deliveryScript, deliveryScript,
req.TargetFeePerKw, req.TargetFeePerKw,
@ -2520,11 +2523,11 @@ func (p *peer) handleLinkFailure(failure linkFailureReport) {
// machine should be passed in. Once the transaction has been sufficiently // machine should be passed in. Once the transaction has been sufficiently
// confirmed, the channel will be marked as fully closed within the database, // confirmed, the channel will be marked as fully closed within the database,
// and any clients will be notified of updates to the closing state. // and any clients will be notified of updates to the closing state.
func (p *peer) finalizeChanClosure(chanCloser *channelCloser) { func (p *peer) finalizeChanClosure(chanCloser *chancloser.ChanCloser) {
closeReq := chanCloser.CloseRequest() closeReq := chanCloser.CloseRequest()
// First, we'll clear all indexes related to the channel in question. // First, we'll clear all indexes related to the channel in question.
chanPoint := chanCloser.cfg.channel.ChannelPoint() chanPoint := chanCloser.Channel().ChannelPoint()
p.WipeChannel(chanPoint) p.WipeChannel(chanPoint)
// Next, we'll launch a goroutine which will request to be notified by // Next, we'll launch a goroutine which will request to be notified by
@ -2558,7 +2561,7 @@ func (p *peer) finalizeChanClosure(chanCloser *channelCloser) {
} }
} }
go waitForChanToClose(chanCloser.negotiationHeight, notifier, errChan, go waitForChanToClose(chanCloser.NegotiationHeight(), notifier, errChan,
chanPoint, &closingTxid, closingTx.TxOut[0].PkScript, func() { chanPoint, &closingTxid, closingTx.TxOut[0].PkScript, func() {
// Respond to the local subsystem which requested the // Respond to the local subsystem which requested the

@ -14,6 +14,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
) )
@ -642,7 +643,7 @@ func TestChooseDeliveryScript(t *testing.T) {
userScript: script1, userScript: script1,
shutdownScript: script2, shutdownScript: script2,
expectedScript: nil, expectedScript: nil,
expectedError: errUpfrontShutdownScriptMismatch, expectedError: chancloser.ErrUpfrontShutdownScriptMismatch,
}, },
{ {
name: "Only upfront script", name: "Only upfront script",
@ -733,7 +734,7 @@ func TestCustomShutdownScript(t *testing.T) {
name: "Shutdown set, user script different", name: "Shutdown set, user script different",
update: setShutdown, update: setShutdown,
userCloseScript: []byte("different addr"), userCloseScript: []byte("different addr"),
expectedError: errUpfrontShutdownScriptMismatch, expectedError: chancloser.ErrUpfrontShutdownScriptMismatch,
}, },
} }

@ -24,6 +24,7 @@ import (
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/shachain" "github.com/lightningnetwork/lnd/shachain"
@ -441,7 +442,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, publTx chan *wire.MsgTx,
activeChannels: make(map[lnwire.ChannelID]*lnwallet.LightningChannel), activeChannels: make(map[lnwire.ChannelID]*lnwallet.LightningChannel),
newChannels: make(chan *newChannelMsg, 1), newChannels: make(chan *newChannelMsg, 1),
activeChanCloses: make(map[lnwire.ChannelID]*channelCloser), activeChanCloses: make(map[lnwire.ChannelID]*chancloser.ChanCloser),
localCloseChanReqs: make(chan *htlcswitch.ChanClose), localCloseChanReqs: make(chan *htlcswitch.ChanClose),
chanCloseMsgs: make(chan *closeMsg), chanCloseMsgs: make(chan *closeMsg),