From ec2d999371df5889533ff15e4f4d6ecb04cd8139 Mon Sep 17 00:00:00 2001 From: nsa Date: Thu, 11 Jun 2020 15:25:05 -0400 Subject: [PATCH 1/3] chancloser+peer: export negotiationHeight, channel, and error --- chancloser.go | 88 ++++++++++++++++++++++++++-------------------- chancloser_test.go | 2 +- peer.go | 40 ++++++++++----------- peer_test.go | 4 +-- 4 files changed, 72 insertions(+), 62 deletions(-) diff --git a/chancloser.go b/chancloser.go index a85db9c9..b7ad0813 100644 --- a/chancloser.go +++ b/chancloser.go @@ -27,10 +27,10 @@ var ( // a message while it is in an unknown 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 // the upfront shutdown script previously set for that party. - errUpfrontShutdownScriptMismatch = fmt.Errorf("shutdown " + + ErrUpfrontShutdownScriptMismatch = fmt.Errorf("shutdown " + "script does not match upfront shutdown script") ) @@ -71,30 +71,30 @@ const ( closeFinished ) -// chanCloseCfg holds all the items that a channelCloser requires to carry out +// ChanCloseCfg holds all the items that a channelCloser requires to carry out // its duties. -type chanCloseCfg struct { - // channel is the channel that should be closed. - channel *lnwallet.LightningChannel +type ChanCloseCfg struct { + // Channel is the channel that should be closed. + Channel *lnwallet.LightningChannel - // unregisterChannel is a function closure that allows the + // UnregisterChannel is a function closure that allows the // channelCloser to re-register a channel. Once this has been done, no // further HTLC's should be routed through the channel. - unregisterChannel func(lnwire.ChannelID) + UnregisterChannel func(lnwire.ChannelID) - // broadcastTx broadcasts the passed transaction to the network. - broadcastTx func(*wire.MsgTx, string) error + // BroadcastTx broadcasts the passed transaction to the network. + 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. - disableChannel func(wire.OutPoint) error + DisableChannel func(wire.OutPoint) error - // disconnect will disconnect from the remote peer in this close. - disconnect func() error + // Disconnect will disconnect from the remote peer in this close. + 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. - quit chan struct{} + Quit chan struct{} } // channelCloser is a state machine that handles the cooperative channel @@ -106,7 +106,7 @@ type channelCloser struct { state closeState // cfg holds the configuration for this channelCloser instance. - cfg chanCloseCfg + cfg ChanCloseCfg // chanPoint is the full channel point of the target channel. chanPoint wire.OutPoint @@ -159,10 +159,10 @@ type channelCloser struct { 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 configuration, and delivery+fee preference. The final argument should // only 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, closeReq *htlcswitch.ChanClose, locallyInitiated bool) *channelCloser { @@ -170,14 +170,14 @@ func newChannelCloser(cfg chanCloseCfg, deliveryScript []byte, // fee will be starting at for this fee negotiation. // // 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 // commitment transaction, then we'll clamp it down to be within the // proper range. // // TODO(roasbeef): clamp fee func? - channelCommitFee := cfg.channel.StateSnapshot().CommitFee + channelCommitFee := cfg.Channel.StateSnapshot().CommitFee if idealFeeSat > channelCommitFee { peerLog.Infof("Ideal starting fee of %v is greater than "+ "commit fee of %v, clamping", int64(idealFeeSat), @@ -187,13 +187,13 @@ func newChannelCloser(cfg chanCloseCfg, deliveryScript []byte, } peerLog.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{ closeReq: closeReq, state: closeIdle, - chanPoint: *cfg.channel.ChannelPoint(), + chanPoint: *cfg.Channel.ChannelPoint(), cid: cid, cfg: cfg, negotiationHeight: negotiationHeight, @@ -217,21 +217,21 @@ func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) { // Before closing, we'll attempt to send a disable update for the // channel. We do so before closing the channel as otherwise the current // edge policy 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 "+ "close: %v", c.chanPoint, err) } // Before returning the shutdown message, we'll unregister the channel // to 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 // nil txn. Even though we haven't negotiated the final txn, this // guarantees that our listchannels rpc will be externally consistent, // and reflect that the channel is being shutdown by the time the // closing request returns. - err := c.cfg.channel.MarkCoopBroadcasted(nil, c.locallyInitiated) + err := c.cfg.Channel.MarkCoopBroadcasted(nil, c.locallyInitiated) if err != nil { return nil, err } @@ -292,6 +292,16 @@ func (c *channelCloser) CloseRequest() *htlcswitch.ChanClose { return c.closeReq } +// Channel returns the channel stored in the config. +func (c *channelCloser) Channel() *lnwallet.LightningChannel { + return c.cfg.Channel +} + +// NegotiationHeight returns the negotiation height. +func (c *channelCloser) NegotiationHeight() uint32 { + return c.negotiationHeight +} + // maybeMatchScript attempts to match the script provided in our peer's // shutdown message with the upfront shutdown script we have on record. // If no upfront shutdown script was set, we do not need to enforce option @@ -320,7 +330,7 @@ func maybeMatchScript(disconnect func() error, return err } - return errUpfrontShutdownScriptMismatch + return ErrUpfrontShutdownScriptMismatch } return nil @@ -350,21 +360,21 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b // wants to close), we'll check if this is a frozen channel or // not. If the channel is frozen as we were also the initiator // of the channel opening, then we'll deny their close attempt. - chanInitiator := c.cfg.channel.IsInitiator() - if !chanInitiator && c.cfg.channel.State().ChanType.IsFrozen() && - c.negotiationHeight < c.cfg.channel.State().ThawHeight { + chanInitiator := c.cfg.Channel.IsInitiator() + if !chanInitiator && c.cfg.Channel.State().ChanType.IsFrozen() && + c.negotiationHeight < c.cfg.Channel.State().ThawHeight { return nil, false, fmt.Errorf("initiator attempting "+ "to co-op close frozen ChannelPoint(%v) "+ "(current_height=%v, thaw_height=%v)", c.chanPoint, c.negotiationHeight, - c.cfg.channel.State().ThawHeight) + c.cfg.Channel.State().ThawHeight) } // If the remote node opened the channel with option upfront shutdown // script, check that the script they provided matches. if err := maybeMatchScript( - c.cfg.disconnect, c.cfg.channel.RemoteUpfrontShutdownScript(), + c.cfg.Disconnect, c.cfg.Channel.RemoteUpfrontShutdownScript(), shutDownMsg.Address, ); err != nil { return nil, false, err @@ -423,7 +433,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b // If the remote node opened the channel with option upfront shutdown // script, check that the script they provided matches. if err := maybeMatchScript( - c.cfg.disconnect, c.cfg.channel.RemoteUpfrontShutdownScript(), + c.cfg.Disconnect, c.cfg.Channel.RemoteUpfrontShutdownScript(), shutDownMsg.Address, ); err != nil { return nil, false, err @@ -445,7 +455,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b // closing proposal, but only if we're the initiator, as // otherwise, the other party will send their first proposal // first. - if c.cfg.channel.IsInitiator() { + if c.cfg.Channel.IsInitiator() { closeSigned, err := c.proposeCloseSigned(c.idealFeeSat) if err != nil { return nil, false, err @@ -519,7 +529,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b return nil, false, err } - closeTx, _, err := c.cfg.channel.CompleteCooperativeClose( + closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose( localSig, remoteSig, c.localDeliveryScript, c.remoteDeliveryScript, remoteProposedFee, ) @@ -531,7 +541,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b // Before publishing the closing tx, we persist it to the // database, such that it can be republished if something goes // wrong. - err = c.cfg.channel.MarkCoopBroadcasted( + err = c.cfg.Channel.MarkCoopBroadcasted( closeTx, c.locallyInitiated, ) if err != nil { @@ -544,7 +554,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b newLogClosure(func() string { return spew.Sdump(closeTx) })) - err = c.cfg.broadcastTx(closeTx, "") + err = c.cfg.BroadcastTx(closeTx, "") if err != nil { return nil, false, err } @@ -582,7 +592,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b // transaction for a channel based on the prior fee negotiations and our // current compromise fee. func (c *channelCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSigned, error) { - rawSig, _, _, err := c.cfg.channel.CreateCloseProposal( + rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal( fee, c.localDeliveryScript, c.remoteDeliveryScript, ) if err != nil { diff --git a/chancloser_test.go b/chancloser_test.go index f7c481d0..e3f2a64c 100644 --- a/chancloser_test.go +++ b/chancloser_test.go @@ -49,7 +49,7 @@ func TestMaybeMatchScript(t *testing.T) { name: "upfront shutdown set, script not ok", shutdownScript: addr1, upfrontScript: addr2, - expectedErr: errUpfrontShutdownScriptMismatch, + expectedErr: ErrUpfrontShutdownScriptMismatch, }, { name: "nil shutdown and empty upfront", diff --git a/peer.go b/peer.go index a43f254b..23485baf 100644 --- a/peer.go +++ b/peer.go @@ -2114,7 +2114,7 @@ out: // As the negotiations failed, we'll reset the // channel state to ensure we act to on-chain // events as normal. - chanCloser.cfg.channel.ResetState() + chanCloser.Channel().ResetState() if chanCloser.CloseRequest() != nil { chanCloser.CloseRequest().Err <- err @@ -2288,16 +2288,16 @@ func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, e return nil, fmt.Errorf("cannot obtain best block") } - chanCloser = newChannelCloser( - chanCloseCfg{ - channel: channel, - unregisterChannel: p.server.htlcSwitch.RemoveLink, - broadcastTx: p.server.cc.wallet.PublishTransaction, - disableChannel: p.server.chanStatusMgr.RequestDisable, - disconnect: func() error { + chanCloser = NewChanCloser( + ChanCloseCfg{ + Channel: channel, + UnregisterChannel: p.server.htlcSwitch.RemoveLink, + BroadcastTx: p.server.cc.wallet.PublishTransaction, + DisableChannel: p.server.chanStatusMgr.RequestDisable, + Disconnect: func() error { return p.server.DisconnectPeer(p.IdentityKey()) }, - quit: p.quit, + Quit: p.quit, }, deliveryScript, feePerKw, @@ -2334,7 +2334,7 @@ func chooseDeliveryScript(upfront, // the upfront shutdown script (because closing out to a different script // would violate upfront shutdown). if !bytes.Equal(upfront, requested) { - return nil, errUpfrontShutdownScriptMismatch + return nil, ErrUpfrontShutdownScriptMismatch } // The user requested script matches the upfront shutdown script, so we @@ -2404,16 +2404,16 @@ func (p *peer) handleLocalCloseReq(req *htlcswitch.ChanClose) { return } - chanCloser := newChannelCloser( - chanCloseCfg{ - channel: channel, - unregisterChannel: p.server.htlcSwitch.RemoveLink, - broadcastTx: p.server.cc.wallet.PublishTransaction, - disableChannel: p.server.chanStatusMgr.RequestDisable, - disconnect: func() error { + chanCloser := NewChanCloser( + ChanCloseCfg{ + Channel: channel, + UnregisterChannel: p.server.htlcSwitch.RemoveLink, + BroadcastTx: p.server.cc.wallet.PublishTransaction, + DisableChannel: p.server.chanStatusMgr.RequestDisable, + Disconnect: func() error { return p.server.DisconnectPeer(p.IdentityKey()) }, - quit: p.quit, + Quit: p.quit, }, deliveryScript, req.TargetFeePerKw, @@ -2524,7 +2524,7 @@ func (p *peer) finalizeChanClosure(chanCloser *channelCloser) { closeReq := chanCloser.CloseRequest() // First, we'll clear all indexes related to the channel in question. - chanPoint := chanCloser.cfg.channel.ChannelPoint() + chanPoint := chanCloser.Channel().ChannelPoint() p.WipeChannel(chanPoint) // Next, we'll launch a goroutine which will request to be notified by @@ -2558,7 +2558,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() { // Respond to the local subsystem which requested the diff --git a/peer_test.go b/peer_test.go index 4669168b..44c6ee52 100644 --- a/peer_test.go +++ b/peer_test.go @@ -642,7 +642,7 @@ func TestChooseDeliveryScript(t *testing.T) { userScript: script1, shutdownScript: script2, expectedScript: nil, - expectedError: errUpfrontShutdownScriptMismatch, + expectedError: ErrUpfrontShutdownScriptMismatch, }, { name: "Only upfront script", @@ -733,7 +733,7 @@ func TestCustomShutdownScript(t *testing.T) { name: "Shutdown set, user script different", update: setShutdown, userCloseScript: []byte("different addr"), - expectedError: errUpfrontShutdownScriptMismatch, + expectedError: ErrUpfrontShutdownScriptMismatch, }, } From 8528ec4568bd2aa1b61cbaa9798331d614f945f4 Mon Sep 17 00:00:00 2001 From: nsa Date: Tue, 16 Jun 2020 20:13:28 -0400 Subject: [PATCH 2/3] lnd: modifying comments in chancloser.go --- chancloser.go | 463 ++++++++++++++++++++++++-------------------------- 1 file changed, 223 insertions(+), 240 deletions(-) diff --git a/chancloser.go b/chancloser.go index b7ad0813..9bafe9b0 100644 --- a/chancloser.go +++ b/chancloser.go @@ -14,24 +14,24 @@ import ( ) var ( - // ErrChanAlreadyClosing is returned when a channel shutdown is - // attempted more than once. + // ErrChanAlreadyClosing is returned when a channel shutdown is attempted + // more than once. ErrChanAlreadyClosing = fmt.Errorf("channel shutdown already initiated") // ErrChanCloseNotFinished is returned when a caller attempts to access - // a field or function that is contingent on the channel closure - // negotiation already being completed. + // a field or function that is contingent on the channel closure negotiation + // already being completed. ErrChanCloseNotFinished = fmt.Errorf("close negotiation not finished") - // ErrInvalidState is returned when the closing state machine receives - // a message while it is in an unknown state. + // ErrInvalidState is returned when the closing state machine receives a + // message while it is in an unknown state. ErrInvalidState = fmt.Errorf("invalid state") // ErrUpfrontShutdownScriptMismatch is returned when a peer or end user - // provides a script to cooperatively close out to which does not match - // the upfront shutdown script previously set for that party. - ErrUpfrontShutdownScriptMismatch = fmt.Errorf("shutdown " + - "script does not match upfront shutdown script") + // provides a cooperative close script which does not match the upfront + // shutdown script previously set for that party. + ErrUpfrontShutdownScriptMismatch = fmt.Errorf("shutdown script does not " + + "match upfront shutdown script") ) // closeState represents all the possible states the channel closer state @@ -43,43 +43,42 @@ type closeState uint8 const ( // closeIdle is the initial starting state. In this state, the state // machine has been instantiated, but no state transitions have been - // attempted. If a state machine receives a message while in this - // state, then it is the responder to an initiated cooperative channel - // closure. + // attempted. If a state machine receives a message while in this state, + // then it is the responder to an initiated cooperative channel closure. closeIdle closeState = iota // closeShutdownInitiated is the state that's transitioned to once the // initiator of a closing workflow sends the shutdown message. At this - // point, they're waiting for the remote party to respond with their - // own shutdown message. After which, they'll both enter the fee - // negotiation phase. + // point, they're waiting for the remote party to respond with their own + // shutdown message. After which, they'll both enter the fee negotiation + // phase. closeShutdownInitiated // closeFeeNegotiation is the third, and most persistent state. Both // parties enter this state after they've sent and received a shutdown // message. During this phase, both sides will send monotonically - // increasing fee requests until one side accepts the last fee rate - // offered by the other party. In this case, the party will broadcast - // the closing transaction, and send the accepted fee to the remote - // party. This then causes a shift into the closeFinished state. + // increasing fee requests until one side accepts the last fee rate offered + // by the other party. In this case, the party will broadcast the closing + // transaction, and send the accepted fee to the remote party. This then + // causes a shift into the closeFinished state. closeFeeNegotiation - // closeFinished is the final state of the state machine. In this, - // state a side has accepted a fee offer and has broadcast the valid - // closing transaction to the network. During this phase, the closing - // transaction becomes available for examination. + // closeFinished is the final state of the state machine. In this state, a + // side has accepted a fee offer and has broadcast the valid closing + // transaction to the network. During this phase, the closing transaction + // becomes available for examination. closeFinished ) -// ChanCloseCfg holds all the items that a channelCloser requires to carry out -// its duties. +// ChanCloseCfg holds all the items that a channelCloser requires to carry out its +// duties. type ChanCloseCfg struct { // Channel is the channel that should be closed. Channel *lnwallet.LightningChannel - // UnregisterChannel is a function closure that allows the - // channelCloser to re-register a channel. Once this has been done, no - // further HTLC's should be routed through the channel. + // UnregisterChannel is a function closure that allows the channelCloser to + // unregister a channel. Once this has been done, no further HTLC's should + // be routed through the channel. UnregisterChannel func(lnwire.ChannelID) // BroadcastTx broadcasts the passed transaction to the network. @@ -97,10 +96,10 @@ type ChanCloseCfg struct { Quit chan struct{} } -// channelCloser is a state machine that handles the cooperative channel -// closure procedure. This includes shutting down a channel, marking it -// ineligible for routing HTLC's, negotiating fees with the remote party, and -// finally broadcasting the fully signed closure transaction to the network. +// channelCloser is a state machine that handles the cooperative channel closure +// procedure. This includes shutting down a channel, marking it ineligible for +// routing HTLC's, negotiating fees with the remote party, and finally +// broadcasting the fully signed closure transaction to the network. type channelCloser struct { // state is the current state of the state machine. state closeState @@ -117,71 +116,69 @@ type channelCloser struct { // negotiationHeight is the height that the fee negotiation begun at. negotiationHeight uint32 - // closingTx is the final, fully signed closing transaction. This will - // only be populated once the state machine shifts to the closeFinished - // state. + // closingTx is the final, fully signed closing transaction. This will only + // be populated once the state machine shifts to the closeFinished state. closingTx *wire.MsgTx // idealFeeSat is the ideal fee that the state machine should initially // offer when starting negotiation. This will be used as a baseline. idealFeeSat btcutil.Amount - // lastFeeProposal is the last fee that we proposed to the remote - // party. We'll use this as a pivot point to rachet our next offer up, - // or down, or simply accept the remote party's prior offer. + // lastFeeProposal is the last fee that we proposed to the remote party. + // We'll use this as a pivot point to ratchet our next offer up, down, or + // simply accept the remote party's prior offer. lastFeeProposal btcutil.Amount - // priorFeeOffers is a map that keeps track of all the proposed fees - // that we've offered during the fee negotiation. We use this map to - // cut the negotiation early if the remote party ever sends an offer - // that we've sent in the past. Once negotiation terminates, we can - // extract the prior signature of our accepted offer from this map. + // priorFeeOffers is a map that keeps track of all the proposed fees that + // we've offered during the fee negotiation. We use this map to cut the + // negotiation early if the remote party ever sends an offer that we've + // sent in the past. Once negotiation terminates, we can extract the prior + // signature of our accepted offer from this map. // // TODO(roasbeef): need to ensure if they broadcast w/ any of our prior // sigs, we are aware of priorFeeOffers map[btcutil.Amount]*lnwire.ClosingSigned - // closeReq is the initial closing request. This will only be populated - // if we're the initiator of this closing negotiation. + // closeReq is the initial closing request. This will only be populated if + // we're the initiator of this closing negotiation. // // TODO(roasbeef): abstract away closeReq *htlcswitch.ChanClose - // localDeliveryScript is the script that we'll send our settled - // channel funds to. + // localDeliveryScript is the script that we'll send our settled channel + // funds to. localDeliveryScript []byte - // remoteDeliveryScript is the script that we'll send the remote - // party's settled channel funds to. + // remoteDeliveryScript is the script that we'll send the remote party's + // settled channel funds to. remoteDeliveryScript []byte // locallyInitiated is true if we initiated the channel close. locallyInitiated bool } -// NewChanCloser creates a new instance of the channel closure given the -// passed configuration, and delivery+fee preference. The final argument should -// only be populated iff, we're the initiator of this closing request. +// NewChanCloser creates a new instance of the channel closure given the passed +// configuration, and delivery+fee preference. The final argument should only +// be populated iff, we're the initiator of this closing request. func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte, idealFeePerKw chainfee.SatPerKWeight, negotiationHeight uint32, closeReq *htlcswitch.ChanClose, locallyInitiated bool) *channelCloser { - // Given the target fee-per-kw, we'll compute what our ideal _total_ - // fee will be starting at for this fee negotiation. + // Given the target fee-per-kw, we'll compute what our ideal _total_ fee + // will be starting at for this fee negotiation. // // TODO(roasbeef): should factor in minimal commit idealFeeSat := cfg.Channel.CalcFee(idealFeePerKw) // If this fee is greater than the fee currently present within the - // commitment transaction, then we'll clamp it down to be within the - // proper range. + // commitment transaction, then we'll clamp it down to be within the proper + // range. // // TODO(roasbeef): clamp fee func? channelCommitFee := cfg.Channel.StateSnapshot().CommitFee if idealFeeSat > channelCommitFee { - peerLog.Infof("Ideal starting fee of %v is greater than "+ - "commit fee of %v, clamping", int64(idealFeeSat), - int64(channelCommitFee)) + peerLog.Infof("Ideal starting fee of %v is greater than commit "+ + "fee of %v, clamping", int64(idealFeeSat), int64(channelCommitFee)) idealFeeSat = channelCommitFee } @@ -207,30 +204,30 @@ func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte, // initChanShutdown begins the shutdown process by un-registering the channel, // and creating a valid shutdown message to our target delivery address. func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) { - // With both items constructed we'll now send the shutdown message for - // this particular channel, advertising a shutdown request to our - // desired closing script. + // With both items constructed we'll now send the shutdown message for this + // particular channel, advertising a shutdown request to our desired + // closing script. shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript) // TODO(roasbeef): err if channel has htlc's? - // Before closing, we'll attempt to send a disable update for the - // channel. We do so before closing the channel as otherwise the current - // edge policy won't be retrievable from the graph. + // Before closing, we'll attempt to send a disable update for the channel. + // We do so before closing the channel as otherwise the current edge policy + // won't be retrievable from the graph. if err := c.cfg.DisableChannel(c.chanPoint); err != nil { - peerLog.Warnf("Unable to disable channel %v on "+ - "close: %v", c.chanPoint, err) + peerLog.Warnf("Unable to disable channel %v on close: %v", + c.chanPoint, err) } - // Before returning the shutdown message, we'll unregister the channel - // to ensure that it isn't seen as usable within the system. + // Before returning the shutdown message, we'll unregister the channel to + // ensure that it isn't seen as usable within the system. c.cfg.UnregisterChannel(c.cid) - // Before continuing, mark the channel as cooperatively closed with a - // nil txn. Even though we haven't negotiated the final txn, this - // guarantees that our listchannels rpc will be externally consistent, - // and reflect that the channel is being shutdown by the time the - // closing request returns. + // Before continuing, mark the channel as cooperatively closed with a nil + // txn. Even though we haven't negotiated the final txn, this guarantees + // that our listchannels rpc will be externally consistent, and reflect + // that the channel is being shutdown by the time the closing request + // returns. err := c.cfg.Channel.MarkCoopBroadcasted(nil, c.locallyInitiated) if err != nil { return nil, err @@ -246,13 +243,13 @@ func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) { // send to the remote party. Upon completion, we enter the // closeShutdownInitiated phase as we await a response. func (c *channelCloser) ShutdownChan() (*lnwire.Shutdown, error) { - // If we attempt to shutdown the channel for the first time, and we're - // not in the closeIdle state, then the caller made an error. + // If we attempt to shutdown the channel for the first time, and we're not + // in the closeIdle state, then the caller made an error. if c.state != closeIdle { return nil, ErrChanAlreadyClosing } - peerLog.Infof("ChannelPoint(%v): initiating shutdown of", c.chanPoint) + peerLog.Infof("ChannelPoint(%v): initiating shutdown", c.chanPoint) shutdownMsg, err := c.initChanShutdown() if err != nil { @@ -260,12 +257,12 @@ func (c *channelCloser) ShutdownChan() (*lnwire.Shutdown, error) { } // With the opening steps complete, we'll transition into the - // closeShutdownInitiated state. In this state, we'll wait until the - // other party sends their version of the shutdown message. + // closeShutdownInitiated state. In this state, we'll wait until the other + // party sends their version of the shutdown message. c.state = closeShutdownInitiated - // Finally, we'll return the shutdown message to the caller so it can - // send it to the remote peer. + // Finally, we'll return the shutdown message to the caller so it can send + // it to the remote peer. return shutdownMsg, nil } @@ -274,7 +271,7 @@ func (c *channelCloser) ShutdownChan() (*lnwire.Shutdown, error) { // NOTE: This transaction is only available if the state machine is in the // closeFinished state. func (c *channelCloser) 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. if c.state != closeFinished { return nil, ErrChanCloseNotFinished @@ -303,14 +300,13 @@ func (c *channelCloser) NegotiationHeight() uint32 { } // maybeMatchScript attempts to match the script provided in our peer's -// shutdown message with the upfront shutdown script we have on record. -// If no upfront shutdown script was set, we do not need to enforce option -// upfront shutdown, so the function returns early. If an upfront script is -// set, we check whether it matches the script provided by our peer. If they -// do not match, we use the disconnect function provided to disconnect from -// the peer. -func maybeMatchScript(disconnect func() error, - upfrontScript, peerScript lnwire.DeliveryAddress) error { +// shutdown message with the upfront shutdown script we have on record. If no +// upfront shutdown script was set, we do not need to enforce option upfront +// shutdown, so the function returns early. If an upfront script is set, we +// check whether it matches the script provided by our peer. If they do not +// match, we use the disconnect function provided to disconnect from the peer. +func maybeMatchScript(disconnect func() error, upfrontScript, + peerScript lnwire.DeliveryAddress) error { // If no upfront shutdown script was set, return early because we do not // need to enforce closure to a specific script. @@ -341,41 +337,43 @@ func maybeMatchScript(disconnect func() error, // 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 channelCloser can be garbage collected. -func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, bool, error) { +func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, + bool, error) { + 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, - // otherwise, this is an attempted invalid state transition. - shutDownMsg, ok := msg.(*lnwire.Shutdown) + // 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)) + 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 as we were also the initiator - // of the channel opening, then we'll deny their close attempt. + // 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 initiator of the channel + // opening, then we'll deny their close attempt. chanInitiator := c.cfg.Channel.IsInitiator() - if !chanInitiator && c.cfg.Channel.State().ChanType.IsFrozen() && - c.negotiationHeight < c.cfg.Channel.State().ThawHeight { + chanState := c.cfg.Channel.State() + if !chanInitiator && chanState.ChanType.IsFrozen() && + c.negotiationHeight < chanState.ThawHeight { - return nil, false, fmt.Errorf("initiator attempting "+ - "to co-op close frozen ChannelPoint(%v) "+ - "(current_height=%v, thaw_height=%v)", - c.chanPoint, c.negotiationHeight, - c.cfg.Channel.State().ThawHeight) + return nil, false, fmt.Errorf("initiator attempting to co-op "+ + "close frozen ChannelPoint(%v) (current_height=%v, "+ + "thaw_height=%v)", c.chanPoint, c.negotiationHeight, + chanState.ThawHeight) } // If the remote node opened the channel with option upfront shutdown // script, check that the script they provided matches. if err := maybeMatchScript( c.cfg.Disconnect, c.cfg.Channel.RemoteUpfrontShutdownScript(), - shutDownMsg.Address, + shutdownMsg.Address, ); err != nil { return nil, false, err } @@ -383,29 +381,29 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b // 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 + c.remoteDeliveryScript = shutdownMsg.Address - // 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 } - peerLog.Infof("ChannelPoint(%v): Responding to shutdown", + peerLog.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. + // 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 - // We'll also craft our initial close proposal in order to keep - // the negotiation moving, but only if we're the negotiator. + // 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 { @@ -414,47 +412,45 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b msgsToSend = append(msgsToSend, closeSigned) } - // We'll return both sets of messages to send to the remote - // party to kick off the fee negotiation process. + // 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. + // 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) + // 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)) + 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 := maybeMatchScript( - c.cfg.Disconnect, c.cfg.Channel.RemoteUpfrontShutdownScript(), - shutDownMsg.Address, + if err := maybeMatchScript(c.cfg.Disconnect, + c.cfg.Channel.RemoteUpfrontShutdownScript(), shutdownMsg.Address, ); err != nil { return nil, false, 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 + c.remoteDeliveryScript = shutdownMsg.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. + // 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 peerLog.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 first proposal - // first. + // 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 { @@ -466,48 +462,45 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b return nil, false, nil - // If we're receiving a message while we're in the fee negotiation - // phase, then this indicates the remote party is responding to a closed - // signed message we sent, or kicking off the process with their own. + // 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. case closeFeeNegotiation: - // First, we'll assert that we're actually getting a - // CloseSigned message, otherwise an invalid state transition - // was attempted. + // First, we'll assert that we're actually getting a ClosingSigned + // message, otherwise an invalid state transition was attempted. closeSignedMsg, ok := msg.(*lnwire.ClosingSigned) if !ok { return nil, false, fmt.Errorf("expected lnwire.ClosingSigned, "+ "instead have %v", spew.Sdump(msg)) } - // We'll compare the proposed total fee, to what we've proposed - // during the negotiations, if it doesn't match any of our - // prior offers, then we'll attempt to rachet the fee closer to + // We'll compare the proposed total fee, to what we've proposed during + // the negotiations. If it doesn't match any of our prior offers, then + // we'll attempt to ratchet the fee closer to remoteProposedFee := closeSignedMsg.FeeSatoshis if _, ok := c.priorFeeOffers[remoteProposedFee]; !ok { - // We'll now attempt to rachet towards a fee deemed - // acceptable by both parties, factoring in our ideal - // fee rate, and the last proposed fee by both sides. + // We'll now attempt to ratchet towards a fee deemed acceptable by + // both parties, factoring in our ideal fee rate, and the last + // proposed fee by both sides. feeProposal := calcCompromiseFee(c.chanPoint, c.idealFeeSat, c.lastFeeProposal, remoteProposedFee, ) - // With our new fee proposal calculated, we'll craft a - // new close signed signature to send to the other - // party so we can continue the fee negotiation - // process. + // With our new fee proposal calculated, we'll craft a new close + // signed signature to send to the other party so we can continue + // the fee negotiation process. closeSigned, err := c.proposeCloseSigned(feeProposal) if err != nil { return nil, false, err } - // If the compromise fee doesn't match what the peer - // proposed, then we'll return this latest close signed - // message so we continue negotiation. + // If the compromise fee doesn't match what the peer proposed, then + // we'll return this latest close signed message so we can continue + // negotiation. if feeProposal != remoteProposedFee { - peerLog.Debugf("ChannelPoint(%v): close tx "+ - "fee disagreement, continuing negotiation", - c.chanPoint) + peerLog.Debugf("ChannelPoint(%v): close tx fee "+ + "disagreement, continuing negotiation", c.chanPoint) return []lnwire.Message{closeSigned}, false, nil } } @@ -515,9 +508,9 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b peerLog.Infof("ChannelPoint(%v) fee of %v accepted, ending "+ "negotiation", c.chanPoint, remoteProposedFee) - // Otherwise, we've agreed on a fee for the closing - // transaction! We'll craft the final closing transaction so - // we can broadcast it to the network. + // Otherwise, we've agreed on a fee for the closing transaction! We'll + // craft the final closing transaction so we can broadcast it to the + // network. matchingSig := c.priorFeeOffers[remoteProposedFee].Signature localSig, err := matchingSig.ToSignature() if err != nil { @@ -530,56 +523,51 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b } closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose( - localSig, remoteSig, c.localDeliveryScript, - c.remoteDeliveryScript, remoteProposedFee, + localSig, remoteSig, c.localDeliveryScript, c.remoteDeliveryScript, + remoteProposedFee, ) if err != nil { return nil, false, err } c.closingTx = closeTx - // Before publishing the closing tx, we persist it to the - // database, such that it can be republished if something goes - // wrong. - err = c.cfg.Channel.MarkCoopBroadcasted( - closeTx, c.locallyInitiated, - ) + // Before publishing the closing tx, we persist it to the database, + // such that it can be republished if something goes wrong. + err = c.cfg.Channel.MarkCoopBroadcasted(closeTx, c.locallyInitiated) if err != nil { return nil, false, err } - // With the closing transaction crafted, we'll now broadcast it - // to the network. + // With the closing transaction crafted, we'll now broadcast it to the + // network. peerLog.Infof("Broadcasting cooperative close tx: %v", newLogClosure(func() string { return spew.Sdump(closeTx) - })) - err = c.cfg.BroadcastTx(closeTx, "") - if err != nil { + }), + ) + if err := c.cfg.BroadcastTx(closeTx, ""); err != nil { return nil, false, err } - // Finally, we'll transition to the closeFinished state, and - // also return the final close signed message we sent. - // Additionally, we return true for the second argument to - // indicate we're finished with the channel closing - // negotiation. + // Finally, we'll transition to the closeFinished state, and also + // return the final close signed message we sent. Additionally, we + // return true for the second argument to indicate we're finished with + // the channel closing negotiation. c.state = closeFinished matchingOffer := c.priorFeeOffers[remoteProposedFee] return []lnwire.Message{matchingOffer}, true, nil - // If we receive a message while in the closeFinished state, then this - // should only be the remote party echoing the last ClosingSigned - // message that we agreed on. + // If we received a message while in the closeFinished state, then this + // should only be the remote party echoing the last ClosingSigned message + // that we agreed on. case closeFinished: if _, ok := msg.(*lnwire.ClosingSigned); !ok { - return nil, false, fmt.Errorf("expected "+ - "lnwire.ClosingSigned, instead have %v", - spew.Sdump(msg)) + return nil, false, fmt.Errorf("expected lnwire.ClosingSigned, "+ + "instead have %v", spew.Sdump(msg)) } - // There's no more to do as both sides should have already - // broadcast the closing transaction at this state. + // There's no more to do as both sides should have already broadcast + // the closing transaction at this state. return nil, true, nil // Otherwise, we're in an unknown state, and can't proceed. @@ -589,8 +577,8 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b } // proposeCloseSigned attempts to propose a new signature for the closing -// transaction for a channel based on the prior fee negotiations and our -// current compromise fee. +// transaction for a channel based on the prior fee negotiations and our current +// compromise fee. func (c *channelCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSigned, error) { rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal( fee, c.localDeliveryScript, c.remoteDeliveryScript, @@ -599,9 +587,8 @@ func (c *channelCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingS return nil, err } - // We'll note our last signature and proposed fee so when the remote - // party responds we'll be able to decide if we've agreed on fees or - // not. + // We'll note our last signature and proposed fee so when the remote party + // responds we'll be able to decide if we've agreed on fees or not. c.lastFeeProposal = fee parsedSig, err := lnwire.NewSigFromSignature(rawSig) if err != nil { @@ -611,9 +598,9 @@ func (c *channelCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingS peerLog.Infof("ChannelPoint(%v): proposing fee of %v sat to close "+ "chan", c.chanPoint, int64(fee)) - // We'll assemble a ClosingSigned message using this information and - // return it to the caller so we can kick off the final stage of the - // channel closure project. + // We'll assemble a ClosingSigned message using this information and return + // it to the caller so we can kick off the final stage of the channel + // closure process. closeSignedMsg := lnwire.NewClosingSigned(c.cid, fee, parsedSig) // We'll also save this close signed, in the case that the remote party @@ -628,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 // consider their fee acceptable if it's within 30% of our fee. func feeInAcceptableRange(localFee, remoteFee btcutil.Amount) bool { - // If our offer is lower than theirs, then we'll accept their - // offer if it's no more than 30% *greater* than our current - // offer. + // If our offer is lower than theirs, then we'll accept their offer if it's + // no more than 30% *greater* than our current offer. if localFee < remoteFee { acceptableRange := localFee + ((localFee * 3) / 10) return remoteFee <= acceptableRange } - // If our offer is greater than theirs, then we'll accept their offer - // if it's no more than 30% *less* than our current offer. + // If our offer is greater than theirs, then we'll accept their offer if + // it's no more than 30% *less* than our current offer. acceptableRange := localFee - ((localFee * 3) / 10) return remoteFee >= acceptableRange } -// rachetFee is our step function used to inch our fee closer to something that -// both sides can agree on. If up is true, then we'll attempt to increase our -// offered fee. Otherwise, if up is false, then we'll attempt to decrease our -// offered fee. -func rachetFee(fee btcutil.Amount, up bool) btcutil.Amount { - // If we need to rachet up, then we'll increase our fee by 10%. +// ratchetFee is our step function used to inch our fee closer to something +// that both sides can agree on. If up is true, then we'll attempt to increase +// our offered fee. Otherwise, if up is false, then we'll attempt to decrease +// our offered fee. +func ratchetFee(fee btcutil.Amount, up bool) btcutil.Amount { + // If we need to ratchet up, then we'll increase our fee by 10%. if up { return fee + ((fee * 1) / 10) } @@ -659,62 +645,59 @@ func rachetFee(fee btcutil.Amount, up bool) btcutil.Amount { // calcCompromiseFee performs the current fee negotiation algorithm, taking // into consideration our ideal fee based on current fee environment, the fee // we last proposed (if any), and the fee proposed by the peer. -func calcCompromiseFee(chanPoint wire.OutPoint, - ourIdealFee, lastSentFee, remoteFee btcutil.Amount) btcutil.Amount { +func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee, + remoteFee btcutil.Amount) btcutil.Amount { // TODO(roasbeef): take in number of rounds as well? - peerLog.Infof("ChannelPoint(%v): computing fee compromise, ideal=%v, "+ - "last_sent=%v, remote_offer=%v", chanPoint, int64(ourIdealFee), + peerLog.Infof("ChannelPoint(%v): computing fee compromise, ideal="+ + "%v, last_sent=%v, remote_offer=%v", chanPoint, int64(ourIdealFee), int64(lastSentFee), int64(remoteFee)) - // Otherwise, we'll need to attempt to make a fee compromise if this is - // the second round, and neither side has agreed on fees. + // Otherwise, we'll need to attempt to make a fee compromise if this is the + // second round, and neither side has agreed on fees. switch { - // If their proposed fee is identical to our ideal fee, then we'll go - // with that as we can short circuit the fee negotiation. Similarly, if - // we haven't sent an offer yet, we'll default to our ideal fee. + // If their proposed fee is identical to our ideal fee, then we'll go with + // that as we can short circuit the fee negotiation. Similarly, if we + // haven't sent an offer yet, we'll default to our ideal fee. case ourIdealFee == remoteFee || lastSentFee == 0: return ourIdealFee // 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 - // over. + // offering, then we can simply return this fee as the negotiation is over. case remoteFee == lastSentFee: return lastSentFee // 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 - // closer to theirs. + // sent, then we'll need to ratchet down in order to move our offer closer + // to theirs. case remoteFee < lastSentFee: - // If the fee is lower, but still acceptable, then we'll just - // return this fee and end the negotiation. + // If the fee is lower, but still acceptable, then we'll just return + // this fee and end the negotiation. if feeInAcceptableRange(lastSentFee, remoteFee) { - peerLog.Infof("ChannelPoint(%v): proposed remote fee "+ - "is close enough, capitulating", chanPoint) + peerLog.Infof("ChannelPoint(%v): proposed remote fee is "+ + "close enough, capitulating", chanPoint) return remoteFee } - // Otherwise, we'll rachet the fee *down* using our current - // algorithm. - return rachetFee(lastSentFee, false) + // Otherwise, we'll ratchet the fee *down* using our current algorithm. + return ratchetFee(lastSentFee, false) - // If the fee the remote party is offering is greater than the last one - // we sent, then we'll rachet up in order to ensure we terminate - // eventually. + // If the fee the remote party is offering is greater than the last one we + // sent, then we'll ratchet up in order to ensure we terminate eventually. case remoteFee > lastSentFee: - // If the fee is greater, but still acceptable, then we'll just - // return this fee in order to put an end to the negotiation. + // If the fee is greater, but still acceptable, then we'll just return + // this fee in order to put an end to the negotiation. if feeInAcceptableRange(lastSentFee, remoteFee) { - peerLog.Infof("ChannelPoint(%v): proposed remote fee "+ - "is close enough, capitulating", chanPoint) + peerLog.Infof("ChannelPoint(%v): proposed remote fee is "+ + "close enough, capitulating", chanPoint) return remoteFee } // Otherwise, we'll rachet the fee up using our current // algorithm. - return rachetFee(lastSentFee, true) + return ratchetFee(lastSentFee, true) default: // TODO(roasbeef): fail if their fee isn't in expected range From 2d68a64a5bdf468ce7cfa02d80e29930c9d3010a Mon Sep 17 00:00:00 2001 From: nsa Date: Tue, 16 Jun 2020 20:33:06 -0400 Subject: [PATCH 3/3] chancloser: new package for cooperative channel closure Introduces a new chancloser package which exposes a ChanCloser struct that handles the cooperative channel closure negotiation and is meant to replace chancloser.go in the lnd package. Updates all references to chancloser.go to instead use chancloser package. --- .../chancloser/chancloser.go | 73 +++++++++---------- .../chancloser/chancloser_test.go | 2 +- lnwallet/chancloser/log.go | 41 +++++++++++ log.go | 2 + peer.go | 21 +++--- peer_test.go | 5 +- test_utils.go | 3 +- 7 files changed, 97 insertions(+), 50 deletions(-) rename chancloser.go => lnwallet/chancloser/chancloser.go (91%) rename chancloser_test.go => lnwallet/chancloser/chancloser_test.go (98%) create mode 100644 lnwallet/chancloser/log.go diff --git a/chancloser.go b/lnwallet/chancloser/chancloser.go similarity index 91% rename from chancloser.go rename to lnwallet/chancloser/chancloser.go index 9bafe9b0..0691d351 100644 --- a/chancloser.go +++ b/lnwallet/chancloser/chancloser.go @@ -1,4 +1,4 @@ -package lnd +package chancloser import ( "bytes" @@ -70,13 +70,13 @@ const ( closeFinished ) -// ChanCloseCfg holds all the items that a channelCloser requires to carry out its +// ChanCloseCfg holds all the items that a ChanCloser requires to carry out its // duties. type ChanCloseCfg struct { // Channel is the channel that should be closed. Channel *lnwallet.LightningChannel - // UnregisterChannel is a function closure that allows the channelCloser to + // UnregisterChannel is a function closure that allows the ChanCloser to // unregister a channel. Once this has been done, no further HTLC's should // be routed through the channel. UnregisterChannel func(lnwire.ChannelID) @@ -96,15 +96,15 @@ type ChanCloseCfg struct { Quit chan struct{} } -// channelCloser is a state machine that handles the cooperative channel closure +// ChanCloser is a state machine that handles the cooperative channel closure // procedure. This includes shutting down a channel, marking it ineligible for // routing HTLC's, negotiating fees with the remote party, and finally // 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 closeState - // cfg holds the configuration for this channelCloser instance. + // cfg holds the configuration for this ChanCloser instance. cfg ChanCloseCfg // chanPoint is the full channel point of the target channel. @@ -162,7 +162,7 @@ type channelCloser struct { // be populated iff, we're the initiator of this closing request. func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte, 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_ fee // will be starting at for this fee negotiation. @@ -177,17 +177,17 @@ func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte, // TODO(roasbeef): clamp fee func? channelCommitFee := cfg.Channel.StateSnapshot().CommitFee if idealFeeSat > channelCommitFee { - peerLog.Infof("Ideal starting fee of %v is greater than commit "+ + chancloserLog.Infof("Ideal starting fee of %v is greater than commit "+ "fee of %v, clamping", int64(idealFeeSat), int64(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)) cid := lnwire.NewChanIDFromOutPoint(cfg.Channel.ChannelPoint()) - return &channelCloser{ + return &ChanCloser{ closeReq: closeReq, state: closeIdle, chanPoint: *cfg.Channel.ChannelPoint(), @@ -203,7 +203,7 @@ func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte, // initChanShutdown begins the shutdown process by un-registering the channel, // 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 this // particular channel, advertising a shutdown request to our desired // closing script. @@ -215,7 +215,7 @@ func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) { // We do so before closing the channel as otherwise the current edge policy // won't be retrievable from the graph. if err := c.cfg.DisableChannel(c.chanPoint); err != nil { - peerLog.Warnf("Unable to disable channel %v on close: %v", + chancloserLog.Warnf("Unable to disable channel %v on close: %v", c.chanPoint, err) } @@ -233,7 +233,8 @@ func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) { return nil, err } - peerLog.Infof("ChannelPoint(%v): sending shutdown message", c.chanPoint) + chancloserLog.Infof("ChannelPoint(%v): sending shutdown message", + c.chanPoint) return shutdown, nil } @@ -242,14 +243,14 @@ func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) { // cooperative channel closure. This message returns the shutdown message to // send to the remote party. Upon completion, we enter the // 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 not // in the closeIdle state, then the caller made an error. if c.state != closeIdle { return nil, ErrChanAlreadyClosing } - peerLog.Infof("ChannelPoint(%v): initiating shutdown", c.chanPoint) + chancloserLog.Infof("ChannelPoint(%v): initiating shutdown", c.chanPoint) shutdownMsg, err := c.initChanShutdown() if err != nil { @@ -270,7 +271,7 @@ func (c *channelCloser) ShutdownChan() (*lnwire.Shutdown, error) { // // NOTE: This transaction is only available if the state machine is in the // 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 // return an error as we haven't yet computed the closing tx. if c.state != closeFinished { @@ -285,17 +286,17 @@ func (c *channelCloser) ClosingTx() (*wire.MsgTx, error) { // // NOTE: This will only return a non-nil pointer if we were the initiator of // the cooperative closure workflow. -func (c *channelCloser) CloseRequest() *htlcswitch.ChanClose { +func (c *ChanCloser) CloseRequest() *htlcswitch.ChanClose { return c.closeReq } // Channel returns the channel stored in the config. -func (c *channelCloser) Channel() *lnwallet.LightningChannel { +func (c *ChanCloser) Channel() *lnwallet.LightningChannel { return c.cfg.Channel } // NegotiationHeight returns the negotiation height. -func (c *channelCloser) NegotiationHeight() uint32 { +func (c *ChanCloser) NegotiationHeight() uint32 { return c.negotiationHeight } @@ -317,7 +318,7 @@ func maybeMatchScript(disconnect func() error, upfrontScript, // If an upfront shutdown script was provided, disconnect from the peer, as // per BOLT 2, and return an error. 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) // Disconnect from the peer because they have violated option upfront @@ -336,8 +337,8 @@ func maybeMatchScript(disconnect func() error, upfrontScript, // 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 channelCloser can be garbage collected. -func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, +// means the ChanCloser can be garbage collected. +func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, bool, error) { switch c.state { @@ -390,7 +391,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, return nil, false, err } - peerLog.Infof("ChannelPoint(%v): responding to shutdown", + chancloserLog.Infof("ChannelPoint(%v): responding to shutdown", c.chanPoint) msgsToSend := make([]lnwire.Message, 0, 2) @@ -445,7 +446,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // closing transaction should look like. c.state = closeFeeNegotiation - peerLog.Infof("ChannelPoint(%v): shutdown response received, "+ + chancloserLog.Infof("ChannelPoint(%v): shutdown response received, "+ "entering fee negotiation", c.chanPoint) // Starting with our ideal fee rate, we'll create an initial closing @@ -482,9 +483,8 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // We'll now attempt to ratchet towards a fee deemed acceptable by // both parties, factoring in our ideal fee rate, and the last // proposed fee by both sides. - feeProposal := calcCompromiseFee(c.chanPoint, - c.idealFeeSat, c.lastFeeProposal, - remoteProposedFee, + feeProposal := calcCompromiseFee(c.chanPoint, c.idealFeeSat, + c.lastFeeProposal, remoteProposedFee, ) // With our new fee proposal calculated, we'll craft a new close @@ -499,13 +499,13 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // we'll return this latest close signed message so we can continue // negotiation. if feeProposal != remoteProposedFee { - peerLog.Debugf("ChannelPoint(%v): close tx fee "+ + chancloserLog.Debugf("ChannelPoint(%v): close tx fee "+ "disagreement, continuing negotiation", c.chanPoint) 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) // Otherwise, we've agreed on a fee for the closing transaction! We'll @@ -540,7 +540,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // With the closing transaction crafted, we'll now broadcast it to the // network. - peerLog.Infof("Broadcasting cooperative close tx: %v", + chancloserLog.Infof("Broadcasting cooperative close tx: %v", newLogClosure(func() string { return spew.Sdump(closeTx) }), @@ -579,7 +579,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // proposeCloseSigned attempts to propose a new signature for the closing // transaction for a channel based on the prior fee negotiations and our current // 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( fee, c.localDeliveryScript, c.remoteDeliveryScript, ) @@ -595,7 +595,7 @@ func (c *channelCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingS 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)) // We'll assemble a ClosingSigned message using this information and return @@ -650,7 +650,7 @@ func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee, // TODO(roasbeef): take in number of rounds as well? - peerLog.Infof("ChannelPoint(%v): computing fee compromise, ideal="+ + chancloserLog.Infof("ChannelPoint(%v): computing fee compromise, ideal="+ "%v, last_sent=%v, remote_offer=%v", chanPoint, int64(ourIdealFee), int64(lastSentFee), int64(remoteFee)) @@ -676,7 +676,7 @@ func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee, // If the fee is lower, but still acceptable, then we'll just return // this fee and end the negotiation. if feeInAcceptableRange(lastSentFee, remoteFee) { - peerLog.Infof("ChannelPoint(%v): proposed remote fee is "+ + chancloserLog.Infof("ChannelPoint(%v): proposed remote fee is "+ "close enough, capitulating", chanPoint) return remoteFee } @@ -690,13 +690,12 @@ func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee, // If the fee is greater, but still acceptable, then we'll just return // this fee in order to put an end to the negotiation. if feeInAcceptableRange(lastSentFee, remoteFee) { - peerLog.Infof("ChannelPoint(%v): proposed remote fee is "+ + chancloserLog.Infof("ChannelPoint(%v): proposed remote fee is "+ "close enough, capitulating", chanPoint) return remoteFee } - // Otherwise, we'll rachet the fee up using our current - // algorithm. + // Otherwise, we'll ratchet the fee up using our current algorithm. return ratchetFee(lastSentFee, true) default: diff --git a/chancloser_test.go b/lnwallet/chancloser/chancloser_test.go similarity index 98% rename from chancloser_test.go rename to lnwallet/chancloser/chancloser_test.go index e3f2a64c..5d517378 100644 --- a/chancloser_test.go +++ b/lnwallet/chancloser/chancloser_test.go @@ -1,4 +1,4 @@ -package lnd +package chancloser import ( "crypto/rand" diff --git a/lnwallet/chancloser/log.go b/lnwallet/chancloser/log.go new file mode 100644 index 00000000..967a1ec3 --- /dev/null +++ b/lnwallet/chancloser/log.go @@ -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) +} diff --git a/log.go b/log.go index 4e3eb831..bf194969 100644 --- a/log.go +++ b/log.go @@ -26,6 +26,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/verrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/lightningnetwork/lnd/monitoring" "github.com/lightningnetwork/lnd/netann" @@ -121,6 +122,7 @@ func SetupLoggers(root *build.RotatingLogWriter) { AddSubLogger(root, "WTCL", wtclient.UseLogger) AddSubLogger(root, "PRNF", peernotifier.UseLogger) AddSubLogger(root, "CHFD", chanfunding.UseLogger) + AddSubLogger(root, "CHCL", chancloser.UseLogger) AddSubLogger(root, routing.Subsystem, routing.UseLogger, localchans.UseLogger) AddSubLogger(root, routerrpc.Subsystem, routerrpc.UseLogger) diff --git a/peer.go b/peer.go index 23485baf..6fe31447 100644 --- a/peer.go +++ b/peer.go @@ -27,6 +27,7 @@ import ( "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/pool" "github.com/lightningnetwork/lnd/queue" @@ -190,7 +191,7 @@ type peer struct { // messages are directed to one of these active state machines. Once // the channel has been closed, the state machine will be delete from // the map. - activeChanCloses map[lnwire.ChannelID]*channelCloser + activeChanCloses map[lnwire.ChannelID]*chancloser.ChanCloser // localCloseChanReqs is a channel in which any local requests to close // 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), - activeChanCloses: make(map[lnwire.ChannelID]*channelCloser), + activeChanCloses: make(map[lnwire.ChannelID]*chancloser.ChanCloser), localCloseChanReqs: make(chan *htlcswitch.ChanClose), linkFailures: make(chan linkFailureReport), chanCloseMsgs: make(chan *closeMsg), @@ -2232,7 +2233,9 @@ func (p *peer) reenableActiveChannels() { // 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 // 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 // not, we'll ignore this message. p.activeChanMtx.RLock() @@ -2288,8 +2291,8 @@ func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, e return nil, fmt.Errorf("cannot obtain best block") } - chanCloser = NewChanCloser( - ChanCloseCfg{ + chanCloser = chancloser.NewChanCloser( + chancloser.ChanCloseCfg{ Channel: channel, UnregisterChannel: p.server.htlcSwitch.RemoveLink, BroadcastTx: p.server.cc.wallet.PublishTransaction, @@ -2334,7 +2337,7 @@ func chooseDeliveryScript(upfront, // the upfront shutdown script (because closing out to a different script // would violate upfront shutdown). if !bytes.Equal(upfront, requested) { - return nil, ErrUpfrontShutdownScriptMismatch + return nil, chancloser.ErrUpfrontShutdownScriptMismatch } // The user requested script matches the upfront shutdown script, so we @@ -2404,8 +2407,8 @@ func (p *peer) handleLocalCloseReq(req *htlcswitch.ChanClose) { return } - chanCloser := NewChanCloser( - ChanCloseCfg{ + chanCloser := chancloser.NewChanCloser( + chancloser.ChanCloseCfg{ Channel: channel, UnregisterChannel: p.server.htlcSwitch.RemoveLink, BroadcastTx: p.server.cc.wallet.PublishTransaction, @@ -2520,7 +2523,7 @@ func (p *peer) handleLinkFailure(failure linkFailureReport) { // machine should be passed in. Once the transaction has been sufficiently // confirmed, the channel will be marked as fully closed within the database, // 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() // First, we'll clear all indexes related to the channel in question. diff --git a/peer_test.go b/peer_test.go index 44c6ee52..48e2a473 100644 --- a/peer_test.go +++ b/peer_test.go @@ -14,6 +14,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwire" ) @@ -642,7 +643,7 @@ func TestChooseDeliveryScript(t *testing.T) { userScript: script1, shutdownScript: script2, expectedScript: nil, - expectedError: ErrUpfrontShutdownScriptMismatch, + expectedError: chancloser.ErrUpfrontShutdownScriptMismatch, }, { name: "Only upfront script", @@ -733,7 +734,7 @@ func TestCustomShutdownScript(t *testing.T) { name: "Shutdown set, user script different", update: setShutdown, userCloseScript: []byte("different addr"), - expectedError: ErrUpfrontShutdownScriptMismatch, + expectedError: chancloser.ErrUpfrontShutdownScriptMismatch, }, } diff --git a/test_utils.go b/test_utils.go index 8155f2f7..0e032ee0 100644 --- a/test_utils.go +++ b/test_utils.go @@ -24,6 +24,7 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/netann" "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), newChannels: make(chan *newChannelMsg, 1), - activeChanCloses: make(map[lnwire.ChannelID]*channelCloser), + activeChanCloses: make(map[lnwire.ChannelID]*chancloser.ChanCloser), localCloseChanReqs: make(chan *htlcswitch.ChanClose), chanCloseMsgs: make(chan *closeMsg),