lnd: modifying comments in chancloser.go

This commit is contained in:
nsa 2020-06-16 20:13:28 -04:00
parent ec2d999371
commit 8528ec4568

@ -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